From 1559003466ee0d34297e064aa940f00d75e2ba7a Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Tue, 15 Oct 2024 23:38:44 +0530 Subject: [PATCH] feat(router): implement post_update_tracker for SessionUpdate Flow and add support for session_update_flow for Paypal (#6299) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 5 + api-reference/openapi_spec.json | 5 + crates/api_models/src/payments.rs | 2 + crates/common_enums/src/enums.rs | 38 ++-- .../src/router_request_types.rs | 3 + .../src/router_response_types.rs | 6 +- crates/hyperswitch_interfaces/src/types.rs | 11 +- crates/router/src/connector/paypal.rs | 138 ++++++++++++++- .../src/connector/paypal/transformers.rs | 165 +++++++++++++++++- crates/router/src/core/payments.rs | 36 ++++ crates/router/src/core/payments/flows.rs | 1 - .../payments/operations/payment_approve.rs | 1 + .../payments/operations/payment_cancel.rs | 1 + .../payments/operations/payment_capture.rs | 1 + .../operations/payment_complete_authorize.rs | 1 + .../payments/operations/payment_confirm.rs | 1 + .../payments/operations/payment_create.rs | 1 + .../operations/payment_post_session_tokens.rs | 1 + .../payments/operations/payment_reject.rs | 1 + .../payments/operations/payment_response.rs | 154 ++++++++++------ .../payments/operations/payment_session.rs | 1 + .../core/payments/operations/payment_start.rs | 1 + .../payments/operations/payment_status.rs | 1 + .../payments/operations/payment_update.rs | 1 + .../payments_incremental_authorization.rs | 1 + .../payments/operations/tax_calculation.rs | 86 ++++----- .../router/src/core/payments/transformers.rs | 3 + crates/router/src/types.rs | 6 +- crates/router/tests/connectors/utils.rs | 4 +- 29 files changed, 541 insertions(+), 135 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index d2cbbe167843..6c19abce7208 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -14405,6 +14405,11 @@ }, "payment_method_type": { "$ref": "#/components/schemas/PaymentMethodType" + }, + "session_id": { + "type": "string", + "description": "Session Id", + "nullable": true } } }, diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index dc983bc9ec79..8644dc6b9ac2 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -17760,6 +17760,11 @@ }, "payment_method_type": { "$ref": "#/components/schemas/PaymentMethodType" + }, + "session_id": { + "type": "string", + "description": "Session Id", + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 8ca9671acc14..5468d750d311 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4812,6 +4812,8 @@ pub struct PaymentsDynamicTaxCalculationRequest { /// Payment method type #[schema(value_type = PaymentMethodType)] pub payment_method_type: api_enums::PaymentMethodType, + /// Session Id + pub session_id: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 528f38ac61ae..bbbff9fc9557 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -404,25 +404,25 @@ pub enum AuthorizationStatus { Unresolved, } -// #[derive( -// Clone, -// Debug, -// Eq, -// PartialEq, -// serde::Deserialize, -// serde::Serialize, -// strum::Display, -// strum::EnumString, -// ToSchema, -// Hash, -// )] -// #[router_derive::diesel_enum(storage_type = "text")] -// #[serde(rename_all = "snake_case")] -// #[strum(serialize_all = "snake_case")] -// pub enum SessionUpdateStatus { -// Success, -// Failure, -// } +#[derive( + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum SessionUpdateStatus { + Success, + Failure, +} #[derive( Clone, diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index f03ab75af19d..d599a2caa749 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -841,6 +841,9 @@ pub struct PaymentsTaxCalculationData { pub struct SdkPaymentsSessionUpdateData { pub order_tax_amount: MinorUnit, pub net_amount: MinorUnit, + pub amount: MinorUnit, + pub currency: storage_enums::Currency, + pub session_id: Option, } #[derive(Debug, Clone)] diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 79a78efb5383..eca56b8c866f 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -68,9 +68,9 @@ pub enum PaymentsResponseData { PostProcessingResponse { session_token: Option, }, - // SessionUpdateResponse { - // status: common_enums::SessionUpdateStatus, - // }, + SessionUpdateResponse { + status: common_enums::SessionUpdateStatus, + }, } #[derive(Debug, Clone)] diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index 81e2086f9721..41a1b1ba83c9 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -9,8 +9,8 @@ use hyperswitch_domain_models::{ payments::{ Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, - PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Session, - SetupMandate, Void, + PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, SdkSessionUpdate, + Session, SetupMandate, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -22,8 +22,8 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, - RetrieveFileRequestData, SetupMandateRequestData, SubmitEvidenceRequestData, - UploadFileRequestData, VerifyWebhookSourceRequestData, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, + SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, @@ -65,6 +65,9 @@ pub type PaymentsPostSessionTokensType = dyn ConnectorIntegration< PaymentsPostSessionTokensData, PaymentsResponseData, >; +/// Type alias for `ConnectorIntegration` +pub type SdkSessionUpdateType = + dyn ConnectorIntegration; /// Type alias for `ConnectorIntegration` pub type SetupMandateType = dyn ConnectorIntegration; diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 254278944e7f..274aab8e5047 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -10,6 +10,7 @@ use common_utils::{ use diesel_models::enums; use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret}; +use router_env::logger; #[cfg(feature = "payouts")] use router_env::{instrument, tracing}; use transformers as paypal; @@ -74,6 +75,7 @@ impl api::RefundExecute for Paypal {} impl api::RefundSync for Paypal {} impl api::ConnectorVerifyWebhookSource for Paypal {} impl api::PaymentPostSessionTokens for Paypal {} +impl api::PaymentSessionUpdate for Paypal {} impl api::Payouts for Paypal {} #[cfg(feature = "payouts")] @@ -482,7 +484,7 @@ impl ConnectorIntegration for Paypal +{ + fn get_headers( + &self, + req: &types::SdkSessionUpdateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &types::SdkSessionUpdateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let session_id = + req.request + .session_id + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "session_id", + })?; + Ok(format!( + "{}v2/checkout/orders/{}", + self.base_url(connectors), + session_id + )) + } + + fn build_request( + &self, + req: &types::SdkSessionUpdateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Patch) + .url(&types::SdkSessionUpdateType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::SdkSessionUpdateType::get_headers( + self, req, connectors, + )?) + .set_body(types::SdkSessionUpdateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn get_request_body( + &self, + req: &types::SdkSessionUpdateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let order_amount = connector_utils::convert_amount( + self.amount_converter, + req.request.amount, + req.request.currency, + )?; + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.net_amount, + req.request.currency, + )?; + let order_tax_amount = connector_utils::convert_amount( + self.amount_converter, + req.request.order_tax_amount, + req.request.currency, + )?; + let connector_router_data = paypal::PaypalRouterData::try_from(( + amount, + Some(order_tax_amount), + Some(order_amount), + req, + ))?; + + let connector_req = paypal::PaypalUpdateOrderRequest::try_from(&connector_router_data)?; + // encode only for for urlencoded things. + Ok(RequestContent::Json(Box::new( + connector_req.get_inner_value(), + ))) + } + + fn handle_response( + &self, + data: &types::SdkSessionUpdateRouterData, + _event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + logger::debug!("Expected zero bytes response, skipped parsing of the response"); + // https://developer.paypal.com/docs/api/orders/v2/#orders_patch + // If 204 status code, then the session was updated successfully. + let status = if res.status_code == 204 { + enums::SessionUpdateStatus::Success + } else { + enums::SessionUpdateStatus::Failure + }; + Ok(types::SdkSessionUpdateRouterData { + response: Ok(types::PaymentsResponseData::SessionUpdateResponse { status }), + ..data.clone() + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration for Paypal { @@ -789,7 +915,7 @@ impl ConnectorIntegration { pub amount: StringMajorUnit, + pub order_tax_amount: Option, + pub order_amount: Option, pub router_data: T, } -impl TryFrom<(StringMajorUnit, T)> for PaypalRouterData { +impl + TryFrom<( + StringMajorUnit, + Option, + Option, + T, + )> for PaypalRouterData +{ type Error = error_stack::Report; - fn try_from((amount, item): (StringMajorUnit, T)) -> Result { + fn try_from( + (amount, order_tax_amount, order_amount, item): ( + StringMajorUnit, + Option, + Option, + T, + ), + ) -> Result { Ok(Self { amount, + order_tax_amount, + order_amount, router_data: item, }) } @@ -55,6 +73,8 @@ pub mod auth_headers { pub const PAYPAL_AUTH_ASSERTION: &str = "PayPal-Auth-Assertion"; } +const ORDER_QUANTITY: u16 = 1; + #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "UPPERCASE")] pub enum PaypalPaymentIntent { @@ -86,6 +106,7 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for OrderReque currency_code: item.router_data.request.currency, value: item.amount.clone(), }, + tax_total: None, }, } } @@ -101,14 +122,46 @@ impl From<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> for Or currency_code: item.router_data.request.currency, value: item.amount.clone(), }, + tax_total: None, }, } } } +impl TryFrom<&PaypalRouterData<&types::SdkSessionUpdateRouterData>> for OrderRequestAmount { + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::SdkSessionUpdateRouterData>, + ) -> Result { + Ok(Self { + currency_code: item.router_data.request.currency, + value: item.amount.clone(), + breakdown: AmountBreakdown { + item_total: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_amount", + }, + )?, + }, + tax_total: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_tax_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_tax_amount", + }, + )?, + }), + }, + }) + } +} + #[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct AmountBreakdown { item_total: OrderAmount, + tax_total: Option, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -133,6 +186,7 @@ pub struct ItemDetails { name: String, quantity: u16, unit_amount: OrderAmount, + tax: Option, } impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ItemDetails { @@ -142,11 +196,12 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ItemDetail "Payment for invoice {}", item.router_data.connector_request_reference_id ), - quantity: 1, + quantity: ORDER_QUANTITY, unit_amount: OrderAmount { currency_code: item.router_data.request.currency, value: item.amount.clone(), }, + tax: None, } } } @@ -158,15 +213,47 @@ impl From<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> for It "Payment for invoice {}", item.router_data.connector_request_reference_id ), - quantity: 1, + quantity: ORDER_QUANTITY, unit_amount: OrderAmount { currency_code: item.router_data.request.currency, value: item.amount.clone(), }, + tax: None, } } } +impl TryFrom<&PaypalRouterData<&types::SdkSessionUpdateRouterData>> for ItemDetails { + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::SdkSessionUpdateRouterData>, + ) -> Result { + Ok(Self { + name: format!( + "Payment for invoice {}", + item.router_data.connector_request_reference_id + ), + quantity: ORDER_QUANTITY, + unit_amount: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_amount", + }, + )?, + }, + tax: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_tax_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_tax_amount", + }, + )?, + }), + }) + } +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct Address { address_line_1: Option>, @@ -211,6 +298,40 @@ impl From<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> for Sh } } +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct PaypalUpdateOrderRequest(Vec); + +impl PaypalUpdateOrderRequest { + pub fn get_inner_value(self) -> Vec { + self.0 + } +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct Operation { + pub op: PaypalOperationType, + pub path: String, + pub value: Value, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum PaypalOperationType { + Add, + Remove, + Replace, + Move, + Copy, + Test, +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum Value { + Amount(OrderRequestAmount), + Items(Vec), +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct ShippingName { full_name: Option>, @@ -461,6 +582,39 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> } } +impl TryFrom<&PaypalRouterData<&types::SdkSessionUpdateRouterData>> for PaypalUpdateOrderRequest { + type Error = error_stack::Report; + + fn try_from( + item: &PaypalRouterData<&types::SdkSessionUpdateRouterData>, + ) -> Result { + let op = PaypalOperationType::Replace; + + // Create separate paths for amount and items + let reference_id = &item.router_data.connector_request_reference_id; + + let amount_path = format!("/purchase_units/@reference_id=='{}'/amount", reference_id); + let items_path = format!("/purchase_units/@reference_id=='{}'/items", reference_id); + + let amount_value = Value::Amount(OrderRequestAmount::try_from(item)?); + + let items_value = Value::Items(vec![ItemDetails::try_from(item)?]); + + Ok(Self(vec![ + Operation { + op: op.clone(), + path: amount_path, + value: amount_value, + }, + Operation { + op, + path: items_path, + value: items_value, + }, + ])) + } +} + impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalPaymentsRequest { type Error = error_stack::Report; fn try_from( @@ -593,7 +747,8 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP | domain::WalletData::WeChatPayQr(_) | domain::WalletData::CashappQr(_) | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + | domain::WalletData::Mifinity(_) + | domain::WalletData::Paze(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Paypal"), ))?, }, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index a4fa22c203ec..13684851e44b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -2972,6 +2972,7 @@ where pub recurring_details: Option, pub poll_config: Option, pub tax_data: Option, + pub session_id: Option, } #[derive(Clone, serde::Serialize, Debug)] @@ -5089,6 +5090,41 @@ pub async fn payments_manual_update( )) } +pub trait PaymentMethodChecker { + fn should_update_in_post_update_tracker(&self) -> bool; + fn should_update_in_update_tracker(&self) -> bool; +} + +#[cfg(feature = "v1")] +impl PaymentMethodChecker for PaymentData { + fn should_update_in_post_update_tracker(&self) -> bool { + let payment_method_type = self + .payment_intent + .tax_details + .as_ref() + .and_then(|tax_details| tax_details.payment_method_type.as_ref().map(|pmt| pmt.pmt)); + + matches!( + payment_method_type, + Some(storage_enums::PaymentMethodType::Paypal) + ) + } + + fn should_update_in_update_tracker(&self) -> bool { + let payment_method_type = self + .payment_intent + .tax_details + .as_ref() + .and_then(|tax_details| tax_details.payment_method_type.as_ref().map(|pmt| pmt.pmt)); + + matches!( + payment_method_type, + Some(storage_enums::PaymentMethodType::ApplePay) + | Some(storage_enums::PaymentMethodType::GooglePay) + ) + } +} + pub trait OperationSessionGetters { fn get_payment_attempt(&self) -> &storage::PaymentAttempt; fn get_payment_intent(&self) -> &storage::PaymentIntent; diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index c888dc74ce08..eae41c91e4e7 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -3041,7 +3041,6 @@ default_imp_for_session_update!( connector::Payeezy, connector::Payme, connector::Payone, - connector::Paypal, connector::Payu, connector::Placetopay, connector::Plaid, diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index c57f4c34458a..e8814c7e56e5 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -192,6 +192,7 @@ impl GetTracker, api::PaymentsCaptureRequest> recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index f21ccb2c5e11..c84dcc50e6d4 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -202,6 +202,7 @@ impl GetTracker, api::PaymentsCancelRequest> recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 710d6dd7cec9..93a79e2d2e30 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -253,6 +253,7 @@ impl GetTracker, api::PaymentsCaptu recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 92e462c78bcb..e705c64d6e0f 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -344,6 +344,7 @@ impl GetTracker, api::PaymentsRequest> for Co recurring_details, poll_config: None, tax_data: None, + session_id: None, }; let customer_details = Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 77ba5676c27d..cdb6fb2f86c8 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -749,6 +749,7 @@ impl GetTracker, api::PaymentsRequest> for Pa recurring_details, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 46f313278fd1..199b2accc68b 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -559,6 +559,7 @@ impl GetTracker, api::PaymentsRequest> for Pa recurring_details, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs index 53fd32acc07b..81702979ac40 100644 --- a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs +++ b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs @@ -164,6 +164,7 @@ impl GetTracker, api::PaymentsPostSessionToke recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { operation: Box::new(self), diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index 040eb92d4590..2257ff0402c1 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -188,6 +188,7 @@ impl GetTracker, PaymentsCancelRequest> for P recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 3375c3dc8057..717b0e607e67 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use api_models::routing::RoutableConnectorChoice; use async_trait::async_trait; -use common_enums::AuthorizationStatus; +use common_enums::{AuthorizationStatus, SessionUpdateStatus}; use common_utils::{ ext_traits::{AsyncExt, Encode}, types::{keymanager::KeyManagerState, MinorUnit}, @@ -25,6 +25,7 @@ use crate::{ core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, mandate, payment_methods, + payment_methods::cards::create_encrypted_data, payments::{ helpers::{ self as payments_helpers, @@ -32,7 +33,7 @@ use crate::{ }, tokenization, types::MultipleCaptureData, - PaymentData, + PaymentData, PaymentMethodChecker, }, utils as core_utils, }, @@ -615,16 +616,16 @@ impl PostUpdateTracker, types::SdkPaymentsSessionUpd { async fn update_tracker<'b>( &'b self, - _db: &'b SessionState, + db: &'b SessionState, _payment_id: &api::PaymentIdType, - payment_data: PaymentData, - _router_data: types::RouterData< + mut payment_data: PaymentData, + router_data: types::RouterData< F, types::SdkPaymentsSessionUpdateData, types::PaymentsResponseData, >, - _key_store: &domain::MerchantKeyStore, - _storage_scheme: enums::MerchantStorageScheme, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, _locale: &Option, #[cfg(feature = "dynamic_routing")] _routable_connector: Vec, #[cfg(feature = "dynamic_routing")] _business_profile: &domain::Profile, @@ -632,53 +633,96 @@ impl PostUpdateTracker, types::SdkPaymentsSessionUpd where F: 'b + Send, { - // let session_update_details = - // payment_data - // .payment_intent - // .tax_details - // .clone() - // .ok_or_else(|| { - // report!(errors::ApiErrorResponse::InternalServerError) - // .attach_printable("missing tax_details in payment_intent") - // })?; - - // let pmt_amount = session_update_details - // .pmt - // .clone() - // .map(|pmt| pmt.order_tax_amount) - // .ok_or(errors::ApiErrorResponse::InternalServerError) - // .attach_printable("Missing tax_details.order_tax_amount")?; - - // let total_amount = MinorUnit::from(payment_data.amount) + pmt_amount; - - // // if connector_ call successful -> payment_intent.amount update - // match router_data.response.clone() { - // Err(_) => (None, None), - // Ok(types::PaymentsResponseData::SessionUpdateResponse { status }) => { - // if status == SessionUpdateStatus::Success { - // ( - // Some( - // storage::PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { - // amount: total_amount, - // amount_capturable: total_amount, - // }, - // ), - // Some( - // storage::PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { - // amount: pmt_amount, - // }, - // ), - // ) - // } else { - // (None, None) - // } - // } - // _ => Err(errors::ApiErrorResponse::InternalServerError) - // .attach_printable("unexpected response in session_update flow")?, - // }; - - // let _shipping_address = payment_data.address.get_shipping(); - // let _amount = payment_data.amount; + let connector = payment_data + .payment_attempt + .connector + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector not found")?; + + // For PayPal, if we call TaxJar for tax calculation, we need to call the connector again to update the order amount so that we can confirm the updated amount and order details. Therefore, we will store the required changes in the database during the post_update_tracker call. + if payment_data.should_update_in_post_update_tracker() { + match router_data.response.clone() { + Ok(types::PaymentsResponseData::SessionUpdateResponse { status }) => { + if status == SessionUpdateStatus::Success { + let shipping_address = payment_data + .tax_data + .clone() + .map(|tax_data| tax_data.shipping_details); + + let shipping_details = shipping_address + .clone() + .async_map(|shipping_details| { + create_encrypted_data(db, key_store, shipping_details) + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt shipping details")?; + + let shipping_address = + payments_helpers::create_or_update_address_for_payment_by_request( + db, + shipping_address.as_ref(), + payment_data.payment_intent.shipping_address_id.as_deref(), + &payment_data.payment_intent.merchant_id, + payment_data.payment_intent.customer_id.as_ref(), + key_store, + &payment_data.payment_intent.payment_id, + storage_scheme, + ) + .await?; + + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::SessionResponseUpdate { + tax_details: payment_data.payment_intent.tax_details.clone().ok_or(errors::ApiErrorResponse::InternalServerError).attach_printable("payment_intent.tax_details not found")?, + shipping_address_id: shipping_address.map(|address| address.address_id), + updated_by: payment_data.payment_intent.updated_by.clone(), + shipping_details, + }; + + let m_db = db.clone().store; + let payment_intent = payment_data.payment_intent.clone(); + let key_manager_state: KeyManagerState = db.into(); + + let updated_payment_intent = m_db + .update_payment_intent( + &key_manager_state, + payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.payment_intent = updated_payment_intent; + } else { + router_data.response.map_err(|err| { + errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector, + status_code: err.status_code, + reason: err.reason, + } + })?; + } + } + Err(err) => { + Err(errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector, + status_code: err.status_code, + reason: err.reason, + })?; + } + _ => { + Err(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unexpected response in session_update flow")?; + } + } + } Ok(payment_data) } @@ -1543,7 +1587,7 @@ async fn payment_response_update_tracker( types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None), - // types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None), + types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None), types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index cc8b839cc809..e78f77234560 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -211,6 +211,7 @@ impl GetTracker, api::PaymentsSessionRequest> recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 4b44933f6dd3..3042b15bfa1a 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -196,6 +196,7 @@ impl GetTracker, api::PaymentsStartRequest> f recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index d28476b67b80..47f16786ab9c 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -505,6 +505,7 @@ async fn get_tracker_for_sync< recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 5dc4168dfcd8..fe7b1ea79b3a 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -479,6 +479,7 @@ impl GetTracker, api::PaymentsRequest> for Pa recurring_details, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 758a17d3fa74..9d80206b9f21 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -170,6 +170,7 @@ impl recurring_details: None, poll_config: None, tax_data: None, + session_id: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs index 246485c353a6..d72d25cdd565 100644 --- a/crates/router/src/core/payments/operations/tax_calculation.rs +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -13,7 +13,7 @@ use crate::{ core::{ errors::{self, RouterResult, StorageErrorExt}, payment_methods::cards::create_encrypted_data, - payments::{self, helpers, operations, PaymentData}, + payments::{self, helpers, operations, PaymentData, PaymentMethodChecker}, utils as core_utils, }, db::errors::ConnectorErrorExt, @@ -177,6 +177,7 @@ impl GetTracker, api::PaymentsDynamicTaxCalcu recurring_details: None, poll_config: None, tax_data: Some(tax_data), + session_id: request.session_id.clone(), }; let get_trackers_response = operations::GetTrackerResponse { operation: Box::new(self), @@ -382,54 +383,61 @@ impl UpdateTracker, api::PaymentsDynamicTaxCalculati where F: 'b + Send, { - let shipping_address = payment_data - .tax_data - .clone() - .map(|tax_data| tax_data.shipping_details); - - let shipping_details = shipping_address - .clone() - .async_map(|shipping_details| create_encrypted_data(state, key_store, shipping_details)) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt shipping details")?; + // For Google Pay and Apple Pay, we don’t need to call the connector again; we can directly confirm the payment after tax_calculation. So, we update the required fields in the database during the update_tracker call. + if payment_data.should_update_in_update_tracker() { + let shipping_address = payment_data + .tax_data + .clone() + .map(|tax_data| tax_data.shipping_details); - let shipping_address = helpers::create_or_update_address_for_payment_by_request( - state, - shipping_address.as_ref(), - payment_data.payment_intent.shipping_address_id.as_deref(), - &payment_data.payment_intent.merchant_id, - payment_data.payment_intent.customer_id.as_ref(), - key_store, - &payment_data.payment_intent.payment_id, - storage_scheme, - ) - .await?; + let shipping_details = shipping_address + .clone() + .async_map(|shipping_details| { + create_encrypted_data(state, key_store, shipping_details) + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt shipping details")?; + + let shipping_address = helpers::create_or_update_address_for_payment_by_request( + state, + shipping_address.as_ref(), + payment_data.payment_intent.shipping_address_id.as_deref(), + &payment_data.payment_intent.merchant_id, + payment_data.payment_intent.customer_id.as_ref(), + key_store, + &payment_data.payment_intent.payment_id, + storage_scheme, + ) + .await?; - let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::SessionResponseUpdate { + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::SessionResponseUpdate { tax_details: payment_data.payment_intent.tax_details.clone().ok_or(errors::ApiErrorResponse::InternalServerError).attach_printable("payment_intent.tax_details not found")?, shipping_address_id: shipping_address.map(|address| address.address_id), updated_by: payment_data.payment_intent.updated_by.clone(), shipping_details, }; - let db = &*state.store; - let payment_intent = payment_data.payment_intent.clone(); + let db = &*state.store; + let payment_intent = payment_data.payment_intent.clone(); - let updated_payment_intent = db - .update_payment_intent( - &state.into(), - payment_intent, - payment_intent_update, - key_store, - storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let updated_payment_intent = db + .update_payment_intent( + &state.into(), + payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - payment_data.payment_intent = updated_payment_intent; - Ok((Box::new(self), payment_data)) + payment_data.payment_intent = updated_payment_intent; + Ok((Box::new(self), payment_data)) + } else { + Ok((Box::new(self), payment_data)) + } } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5d253f44dd4c..72e6c6b9979a 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2110,6 +2110,9 @@ impl TryFrom> for types::SdkPaymentsSessi Ok(Self { net_amount, order_tax_amount, + currency: payment_data.currency, + amount: payment_data.payment_intent.amount, + session_id: payment_data.session_id, }) } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 6f0448ef7772..35857154332c 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -84,8 +84,8 @@ pub use hyperswitch_interfaces::types::{ PaymentsCompleteAuthorizeType, PaymentsInitType, PaymentsPostProcessingType, PaymentsPostSessionTokensType, PaymentsPreAuthorizeType, PaymentsPreProcessingType, PaymentsSessionType, PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, - RefundSyncType, Response, RetrieveFileType, SetupMandateType, SubmitEvidenceType, - TokenizationType, UploadFileType, VerifyWebhookSourceType, + RefundSyncType, Response, RetrieveFileType, SdkSessionUpdateType, SetupMandateType, + SubmitEvidenceType, TokenizationType, UploadFileType, VerifyWebhookSourceType, }; #[cfg(feature = "payouts")] pub use hyperswitch_interfaces::types::{ @@ -168,6 +168,8 @@ pub type PaymentsSessionResponseRouterData = ResponseRouterData; pub type PaymentsInitResponseRouterData = ResponseRouterData; +pub type SdkSessionUpdateResponseRouterData = + ResponseRouterData; pub type PaymentsCaptureResponseRouterData = ResponseRouterData; pub type PaymentsPreprocessingResponseRouterData = diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index c68032369a1d..5acfd67c5470 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -567,7 +567,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, - // Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, Err(_) => None, } } @@ -1079,7 +1079,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, - // Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, Err(_) => None, } }