From ae37436c4feaa380fdab9041773cb4d69fada5b4 Mon Sep 17 00:00:00 2001 From: swangi-kumari Date: Fri, 17 May 2024 18:26:04 +0530 Subject: [PATCH 01/18] refactor: modify complete authorize --- crates/api_models/src/events/payment.rs | 16 ++- crates/api_models/src/payments.rs | 9 ++ crates/diesel_models/src/payment_intent.rs | 9 ++ .../src/payments/payment_intent.rs | 9 ++ crates/openapi/src/openapi.rs | 1 + .../operations/payment_complete_authorize.rs | 38 +++++-- crates/router/src/routes/app.rs | 4 + crates/router/src/routes/lock_utils.rs | 3 +- crates/router/src/routes/payments.rs | 103 +++++++++++++++++- crates/router/src/types/api/payments.rs | 13 ++- crates/router_env/src/logger/types.rs | 2 + .../src/payments/payment_intent.rs | 5 + 12 files changed, 192 insertions(+), 20 deletions(-) diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 59e65c0605f5..cd1671b2b8e0 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -11,10 +11,10 @@ use crate::{ ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsExternalAuthenticationRequest, - PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, - PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest, - PaymentsStartRequest, RedirectionResponse, + PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, + PaymentsIncrementalAuthorizationRequest, PaymentsRejectRequest, PaymentsRequest, + PaymentsResponse, PaymentsRetrieveRequest, PaymentsStartRequest, RedirectionResponse, }, }; impl ApiEventMetric for PaymentsRetrieveRequest { @@ -44,6 +44,14 @@ impl ApiEventMetric for PaymentsCaptureRequest { } } +impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + impl ApiEventMetric for PaymentsCancelRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index dcabbde7ebef..1cb98859beec 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4320,6 +4320,15 @@ pub struct PaymentRetrieveBodyWithCredentials { pub merchant_connector_details: Option, } +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsCompleteAuthorizeRequest { + /// The unique identifier for the payment + #[serde(skip_deserializing)] + pub payment_id: String, + /// The shipping address for the payment + pub shipping: Option
, +} + #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentsCancelRequest { /// The identifier for the payment diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index ee6e3960b735..bea258784316 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -199,6 +199,9 @@ pub enum PaymentIntentUpdate { AuthorizationCountUpdate { authorization_count: i32, }, + CompleteAuthorizeUpdate { + shipping_address_id: Option, + }, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -494,6 +497,12 @@ impl From for PaymentIntentUpdateInternal { authorization_count: Some(authorization_count), ..Default::default() }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + } => Self { + shipping_address_id, + ..Default::default() + }, } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index a5e631b5ac33..a953d9f6fda4 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -201,6 +201,9 @@ pub enum PaymentIntentUpdate { AuthorizationCountUpdate { authorization_count: i32, }, + CompleteAuthorizeUpdate { + shipping_address_id: Option, + }, } #[derive(Clone, Debug, Default)] @@ -420,6 +423,12 @@ impl From for PaymentIntentUpdateInternal { authorization_count: Some(authorization_count), ..Default::default() }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + } => Self { + shipping_address_id, + ..Default::default() + }, } } } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 5f5597387b1e..273e83f6f902 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -402,6 +402,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CaptureResponse, api_models::payments::PaymentsIncrementalAuthorizationRequest, api_models::payments::IncrementalAuthorizationResponse, + api_models::payments::PaymentsCompleteAuthorizeRequest, api_models::payments::PaymentsExternalAuthenticationRequest, api_models::payments::PaymentsExternalAuthenticationResponse, api_models::payments::SdkInformation, 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 06627920d711..18d4853b28cf 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -173,16 +173,25 @@ impl GetTracker, api::PaymentsRequest> for Co .or_else(|| request.customer_id.clone()), )?; - let shipping_address = helpers::get_address_by_id( + let shipping_address = helpers::create_or_update_address_for_payment_by_request( db, - payment_intent.shipping_address_id.clone(), + request.shipping.as_ref(), + payment_intent.shipping_address_id.clone().as_deref(), + merchant_id.as_ref(), + payment_intent + .customer_id + .as_ref() + .or(payment_intent.customer_id.as_ref()), key_store, - &payment_intent.payment_id, - merchant_id, - merchant_account.storage_scheme, + payment_id.as_ref(), + storage_scheme, ) .await?; + payment_intent.shipping_address_id = shipping_address + .as_ref() + .map(|shipping_address| shipping_address.address_id.clone()); + let billing_address = helpers::get_address_by_id( db, payment_intent.billing_address_id.clone(), @@ -422,11 +431,11 @@ impl UpdateTracker, api::PaymentsRequest> for Comple #[instrument(skip_all)] async fn update_trackers<'b>( &'b self, - _state: &'b AppState, + state: &'b AppState, _req_state: ReqState, - payment_data: PaymentData, + mut payment_data: PaymentData, _customer: Option, - _storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, _merchant_key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, @@ -435,6 +444,19 @@ impl UpdateTracker, api::PaymentsRequest> for Comple where F: 'b + Send, { + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id: payment_data.payment_intent.shipping_address_id.clone() + }; + + let db = &*state.store; + let payment_intent = payment_data.payment_intent.clone(); + + let updated_payment_intent = db + .update_payment_intent(payment_intent, payment_intent_update, storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.payment_intent = updated_payment_intent; Ok((Box::new(self), payment_data)) } } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 21cc994381eb..41d7596e4db6 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -389,6 +389,10 @@ impl Payments { .route(web::get().to(payments_complete_authorize)) .route(web::post().to(payments_complete_authorize)), ) + .service( + web::resource("/{payment_id}/complete_authorize") + .route(web::post().to(payments_complete_authorize_flow)), + ) .service( web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments_incremental_authorization)), ) diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 30b582079e32..6a73643ce680 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -121,7 +121,8 @@ impl From for ApiIdentifier { | Flow::PaymentsIncrementalAuthorization | Flow::PaymentsExternalAuthentication | Flow::PaymentsAuthorize - | Flow::GetExtendedCardInfo => Self::Payments, + | Flow::GetExtendedCardInfo + | Flow::PaymentsCompleteAuthorize => Self::Payments, Flow::PayoutsCreate | Flow::PayoutsRetrieve diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 6a1b424a3300..bf16f7e9a858 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -5,7 +5,7 @@ use crate::{ pub mod helpers; use actix_web::{web, Responder}; -use api_models::payments::HeaderPayload; +use api_models::payments::{Address, HeaderPayload, PhoneDetails}; use error_stack::report; use router_env::{env, instrument, tracing, types, Flow}; @@ -792,6 +792,91 @@ pub async fn payments_complete_authorize( ) .await } + +/// Payments - Complete Authorize +#[instrument(skip_all, fields(flow =? Flow::PaymentsCompleteAuthorize, payment_id))] +pub async fn payments_complete_authorize_flow( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsCompleteAuthorize; + let mut payload = json_payload.into_inner(); + + let payment_id = path.into_inner(); + payload.payment_id = payment_id.clone(); + + tracing::Span::current().record("payment_id", &payment_id); + + let shipping = payload.shipping.clone(); + + let address = shipping.clone().and_then(|shipping| shipping.address); + + let payment_confirm_req = payment_types::PaymentsRequest { + payment_id: Some(payment_types::PaymentIdType::PaymentIntentId( + payment_id.clone(), + )), + shipping: Some(Address { + address: Some(api_models::payments::AddressDetails { + city: address.clone().and_then(|address| address.city).clone(), + country: address.clone().and_then(|address| address.country), + line1: address.clone().and_then(|address| address.line1).clone(), + line2: address.clone().and_then(|address| address.line2).clone(), + line3: address.clone().and_then(|address| address.line3).clone(), + zip: address.clone().and_then(|address| address.zip).clone(), + state: address.clone().and_then(|address| address.state).clone(), + first_name: address + .clone() + .and_then(|address| address.first_name) + .clone(), + last_name: address.clone().and_then(|address| address.last_name), + }), + phone: Some(PhoneDetails { + number: shipping + .clone() + .and_then(|shipping| shipping.phone.and_then(|phone| phone.number)), + country_code: shipping + .clone() + .and_then(|shipping| shipping.phone.and_then(|phone| phone.country_code)), + }), + email: shipping.and_then(|shipping| shipping.email), + }), + ..Default::default() + }; + + let locking_action = payload.get_locking_input(flow.clone()); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, _req, req_state| { + payments::payments_core::< + api_types::CompleteAuthorize, + payment_types::PaymentsResponse, + _, + _, + _, + >( + state.clone(), + req_state, + auth.merchant_account, + auth.key_store, + payments::operations::payment_complete_authorize::CompleteAuthorize, + payment_confirm_req.clone(), + api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + ) + }, + &auth::PublishableKeyAuth, + locking_action, + )) + .await +} + /// Payments - Cancel /// /// A Payment could can be cancelled when it is in one of these statuses: requires_payment_method, requires_capture, requires_confirmation, requires_customer_action @@ -1465,6 +1550,22 @@ impl GetLockingInput for payments::PaymentsRedirectResponseData { } } +impl GetLockingInput for payment_types::PaymentsCompleteAuthorizeRequest { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.payment_id.to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + impl GetLockingInput for payment_types::PaymentsCancelRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 9f68cac98c9f..a22019362993 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -6,12 +6,13 @@ pub use api_models::payments::{ PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsApproveRequest, - PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsExternalAuthenticationRequest, - PaymentsIncrementalAuthorizationRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, - PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, - PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, - PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, TimeRange, UrlDetails, - VerifyRequest, VerifyResponse, WalletData, + PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, + PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, + PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, + PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, PhoneDetails, + RedirectionResponse, SessionToken, TimeRange, UrlDetails, VerifyRequest, VerifyResponse, + WalletData, }; use error_stack::ResultExt; diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index ffa492bc60d6..07a45f5cdbc5 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -180,6 +180,8 @@ pub enum Flow { PayoutsAccounts, /// Payments Redirect flow. PaymentsRedirect, + /// Payemnts Complete Authorize Flow + PaymentsCompleteAuthorize, /// Refunds create flow. RefundsCreate, /// Refunds retrieve flow. diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 2cbcbaaf01e6..383363ec01fe 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -1145,6 +1145,11 @@ impl DataModelExt for PaymentIntentUpdate { } => DieselPaymentIntentUpdate::AuthorizationCountUpdate { authorization_count, }, + Self::CompleteAuthorizeUpdate { + shipping_address_id, + } => DieselPaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + }, } } From 0aa1fe3f1b12f5fef354c196f45095a7c73a070f Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 12:58:38 +0000 Subject: [PATCH 02/18] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 91eaf3595850..4ce5caa66b13 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -13514,6 +13514,19 @@ } } }, + "PaymentsCompleteAuthorizeRequest": { + "type": "object", + "properties": { + "shipping": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + } + } + }, "PaymentsConfirmRequest": { "type": "object", "properties": { From b473b0dc3d7a205787eaec6e205ab289f0b43528 Mon Sep 17 00:00:00 2001 From: swangi-kumari Date: Sat, 18 May 2024 14:37:21 +0530 Subject: [PATCH 03/18] refactor: clippy --- crates/router/src/routes/payments.rs | 49 +++++++++++++++------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 973c4c159c6c..0c13e4b912ea 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -813,33 +813,38 @@ pub async fn payments_complete_authorize_flow( let address = shipping.clone().and_then(|shipping| shipping.address); + let address_details = address + .as_ref() + .map(|address| api_models::payments::AddressDetails { + city: address.city.clone(), + country: address.country, + line1: address.line1.clone(), + line2: address.line2.clone(), + line3: address.line3.clone(), + zip: address.zip.clone(), + state: address.state.clone(), + first_name: address.first_name.clone(), + last_name: address.last_name.clone(), + }); + + let phone_details = shipping.as_ref().map(|shipping| PhoneDetails { + number: shipping + .phone + .as_ref() + .and_then(|phone| phone.number.clone()), + country_code: shipping + .phone + .as_ref() + .and_then(|phone| phone.country_code.clone()), + }); + let payment_confirm_req = payment_types::PaymentsRequest { payment_id: Some(payment_types::PaymentIdType::PaymentIntentId( payment_id.clone(), )), shipping: Some(Address { - address: Some(api_models::payments::AddressDetails { - city: address.clone().and_then(|address| address.city).clone(), - country: address.clone().and_then(|address| address.country), - line1: address.clone().and_then(|address| address.line1).clone(), - line2: address.clone().and_then(|address| address.line2).clone(), - line3: address.clone().and_then(|address| address.line3).clone(), - zip: address.clone().and_then(|address| address.zip).clone(), - state: address.clone().and_then(|address| address.state).clone(), - first_name: address - .clone() - .and_then(|address| address.first_name) - .clone(), - last_name: address.clone().and_then(|address| address.last_name), - }), - phone: Some(PhoneDetails { - number: shipping - .clone() - .and_then(|shipping| shipping.phone.and_then(|phone| phone.number)), - country_code: shipping - .clone() - .and_then(|shipping| shipping.phone.and_then(|phone| phone.country_code)), - }), + address: address_details, + phone: phone_details, email: shipping.and_then(|shipping| shipping.email), }), ..Default::default() From 4719a940ffd5379cb0f563d07a5e219d5880ef4e Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Sun, 19 May 2024 23:05:33 +0530 Subject: [PATCH 04/18] feat: add session token from mca_metadata for paypal sdk --- crates/api_models/src/payments.rs | 13 +++++++ crates/connector_configs/src/common_config.rs | 8 ++++ crates/connector_configs/src/connector.rs | 3 +- .../src/response_modifier.rs | 1 + crates/connector_configs/src/transformer.rs | 7 +++- .../connector_configs/toml/development.toml | 2 + crates/connector_configs/toml/production.toml | 2 + crates/connector_configs/toml/sandbox.toml | 2 + .../src/connector/braintree/transformers.rs | 1 + .../src/connector/paypal/transformers.rs | 3 +- .../src/core/payments/flows/session_flow.rs | 38 +++++++++++++++++++ .../payments/operations/payment_session.rs | 1 + crates/router/src/types/api.rs | 1 + 13 files changed, 78 insertions(+), 4 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 0e988a6ff1be..b1216be6c78e 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3981,6 +3981,17 @@ pub struct GpaySessionTokenData { pub data: GpayMetaData, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaypalSdkMetaData { + pub client_id: String, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaypalSdkSessionTokenData { + #[serde(rename = "paypal_sdk")] + pub data: PaypalSdkMetaData, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApplepaySessionRequest { @@ -4158,6 +4169,8 @@ pub struct KlarnaSessionTokenResponse { #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(rename_all = "lowercase")] pub struct PaypalSessionTokenResponse { + /// Name of the connector + pub connector: String, /// The session token for PayPal pub session_token: String, } diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index b11a74a3ca61..fff210b6e4dc 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -54,6 +54,12 @@ pub enum GooglePayData { Zen(ZenGooglePay), } +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaypalSdkData { + pub client_id: Option, +} + #[serde_with::skip_serializing_none] #[derive(Debug, Deserialize, serde::Serialize, Clone)] #[serde(untagged)] @@ -79,6 +85,7 @@ pub struct ApiModelMetaData { pub terminal_id: Option, pub merchant_id: Option, pub google_pay: Option, + pub paypal_sdk: Option, pub apple_pay: Option, pub apple_pay_combined: Option, pub endpoint_prefix: Option, @@ -180,6 +187,7 @@ pub struct DashboardMetaData { pub terminal_id: Option, pub merchant_id: Option, pub google_pay: Option, + pub paypal_sdk: Option, pub apple_pay: Option, pub apple_pay_combined: Option, pub endpoint_prefix: Option, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index e1d55e393855..7118c87b0925 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -10,7 +10,7 @@ use serde::Deserialize; #[cfg(any(feature = "sandbox", feature = "development", feature = "production"))] use toml; -use crate::common_config::{CardProvider, GooglePayData, Provider, ZenApplePay}; +use crate::common_config::{CardProvider, GooglePayData, PaypalSdkData, Provider, ZenApplePay}; #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Classic { @@ -79,6 +79,7 @@ pub struct ConfigMetadata { pub account_name: Option, pub terminal_id: Option, pub google_pay: Option, + pub paypal_sdk: Option, pub apple_pay: Option, pub merchant_id: Option, pub endpoint_prefix: Option, diff --git a/crates/connector_configs/src/response_modifier.rs b/crates/connector_configs/src/response_modifier.rs index 8f7da58c4dcc..ebf97d831622 100644 --- a/crates/connector_configs/src/response_modifier.rs +++ b/crates/connector_configs/src/response_modifier.rs @@ -309,6 +309,7 @@ impl From for DashboardMetaData { terminal_id: api_model.terminal_id, merchant_id: api_model.merchant_id, google_pay: get_google_pay_metadata_response(api_model.google_pay), + paypal_sdk: api_model.paypal_sdk, apple_pay: api_model.apple_pay, apple_pay_combined: api_model.apple_pay_combined, endpoint_prefix: api_model.endpoint_prefix, diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index 01332c9d29ee..ae4b02067676 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -46,7 +46,9 @@ impl DashboardRequestPayload { (Connector::Zen, GooglePay) | (Connector::Zen, ApplePay) => { Some(api_models::enums::PaymentExperience::RedirectToUrl) } - (Connector::Braintree, Paypal) | (Connector::Klarna, Klarna) => { + (Connector::Paypal, Paypal) + | (Connector::Braintree, Paypal) + | (Connector::Klarna, Klarna) => { Some(api_models::enums::PaymentExperience::InvokeSdkClient) } (Connector::Globepay, AliPay) @@ -194,6 +196,7 @@ impl DashboardRequestPayload { three_ds_requestor_name: None, three_ds_requestor_id: None, pull_mechanism_for_external_3ds_enabled: None, + paypal_sdk: None, }; let meta_data = match request.metadata { Some(data) => data, @@ -205,6 +208,7 @@ impl DashboardRequestPayload { let merchant_id = meta_data.merchant_id.clone(); let terminal_id = meta_data.terminal_id.clone(); let endpoint_prefix = meta_data.endpoint_prefix.clone(); + let paypal_sdk = meta_data.paypal_sdk; let apple_pay = meta_data.apple_pay; let apple_pay_combined = meta_data.apple_pay_combined; let merchant_config_currency = meta_data.merchant_config_currency; @@ -228,6 +232,7 @@ impl DashboardRequestPayload { merchant_config_currency, apple_pay_combined, endpoint_prefix, + paypal_sdk, mcc, merchant_country_code, merchant_name, diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index d9e7ec58f755..fcccf9c7e285 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1645,6 +1645,8 @@ api_key="Client Secret" key1="Client ID" [paypal.connector_webhook_details] merchant_secret="Source verification key" +[paypal.metadata.paypal_sdk] +client_id="Client ID" [paypal_payout] [[paypal.wallet]] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 202bce64c1fd..96fba850e7ee 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1265,6 +1265,8 @@ api_key="Client Secret" key1="Client ID" [paypal.connector_webhook_details] merchant_secret="Source verification key" +[paypal.metadata.paypal_sdk] +client_id="Client ID" [payu] [[payu.credit]] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 450e14d1080a..932352f20e46 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1645,6 +1645,8 @@ api_key="Client Secret" key1="Client ID" [paypal.connector_webhook_details] merchant_secret="Source verification key" +[paypal.metadata.paypal_sdk] +client_id="Client ID" [paypal_payout] [[paypal.wallet]] diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index e278ff40b437..1eef83b95fb1 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -307,6 +307,7 @@ impl session_token: api::SessionToken::Paypal(Box::new( payments::PaypalSessionTokenResponse { session_token: item.response.client_token.value.expose(), + connector: "braintree".to_string(), }, )), }), diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 171d07f6517e..e3aed898f96d 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -440,7 +440,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP }) } domain::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { - domain::WalletData::PaypalRedirect(_) => { + domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) => { let intent = if item.router_data.request.is_auto_capture()? { PaypalPaymentIntent::Capture } else { @@ -502,7 +502,6 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP | domain::WalletData::GooglePayThirdPartySdk(_) | domain::WalletData::MbWayRedirect(_) | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 2eb0c921bbf2..12e5c2f95012 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -657,6 +657,41 @@ where ) -> RouterResult; } +fn create_paypal_sdk_session_token( + _state: &routes::AppState, + router_data: &types::PaymentsSessionRouterData, + connector: &api::ConnectorData, + _business_profile: &storage::business_profile::BusinessProfile, +) -> RouterResult { + let connector_metadata = router_data.connector_meta_data.clone(); + + let paypal_sdk_data = connector_metadata + .clone() + .parse_value::("PaypalSdkSessionTokenData") + .change_context(errors::ConnectorError::NoConnectorMetaData) + .attach_printable(format!( + "cannot parse paypal_sdk metadata from the given value {connector_metadata:?}" + )) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_metadata".to_string(), + expected_format: "paypal_sdk_metadata_format".to_string(), + })?; + + // [TODO: Add Dynamic fields support for payal sdk] + + Ok(types::PaymentsSessionRouterData { + response: Ok(types::PaymentsResponseData::SessionResponse { + session_token: payment_types::SessionToken::Paypal(Box::new( + payment_types::PaypalSessionTokenResponse { + connector: connector.connector_name.to_string(), + session_token: paypal_sdk_data.data.client_id, + }, + )), + }), + ..router_data.clone() + }) +} + #[async_trait] impl RouterDataSession for types::PaymentsSessionRouterData { async fn decide_flow<'a, 'b>( @@ -674,6 +709,9 @@ impl RouterDataSession for types::PaymentsSessionRouterData { api::GetToken::ApplePayMetadata => { create_applepay_session_token(state, self, connector, business_profile).await } + api::GetToken::PaypalSdkMetadata => { + create_paypal_sdk_session_token(state, self, connector, business_profile) + } api::GetToken::Connector => { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index e4d91d4a0681..2aa01cd587fa 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -471,6 +471,7 @@ impl From for api::GetToken { match value { api_models::enums::PaymentMethodType::GooglePay => Self::GpayMetadata, api_models::enums::PaymentMethodType::ApplePay => Self::ApplePayMetadata, + api_models::enums::PaymentMethodType::Paypal => Self::PaypalSdkMetadata, _ => Self::Connector, } } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 2013f4827503..aea5e326cf75 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -217,6 +217,7 @@ type BoxedConnector = Box<&'static (dyn Connector + Sync)>; pub enum GetToken { GpayMetadata, ApplePayMetadata, + PaypalSdkMetadata, Connector, } From f2a0fbafc1279342b8493041474a6da5aba4d71e Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Sun, 19 May 2024 17:39:02 +0000 Subject: [PATCH 05/18] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 2465fc5118d9..7c7a7cbdd016 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -16587,9 +16587,14 @@ "PaypalSessionTokenResponse": { "type": "object", "required": [ + "connector", "session_token" ], "properties": { + "connector": { + "type": "string", + "description": "Name of the connector" + }, "session_token": { "type": "string", "description": "The session token for PayPal" From def51f62b2cf67533b80dde7d4c8aa1d9573c40b Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Mon, 20 May 2024 00:21:56 +0530 Subject: [PATCH 06/18] chore: address clippy warnings --- crates/router/src/routes/payments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 0c13e4b912ea..4717f860b758 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -805,7 +805,7 @@ pub async fn payments_complete_authorize_flow( let mut payload = json_payload.into_inner(); let payment_id = path.into_inner(); - payload.payment_id = payment_id.clone(); + payload.payment_id.clone_from(&payment_id); tracing::Span::current().record("payment_id", &payment_id); From 0dcb2699bed5636237177976859e03aff5edc437 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Tue, 21 May 2024 19:22:16 +0530 Subject: [PATCH 07/18] add next action in session call and shipping in dynamic fields --- crates/api_models/src/payments.rs | 4 + crates/router/src/configs/defaults.rs | 162 ++++++++++++++++++ .../src/connector/braintree/transformers.rs | 3 + .../src/core/payments/flows/session_flow.rs | 5 +- 4 files changed, 172 insertions(+), 2 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b1216be6c78e..5c3bc8ed1163 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4173,6 +4173,8 @@ pub struct PaypalSessionTokenResponse { pub connector: String, /// The session token for PayPal pub session_token: String, + /// The next action for the sdk (ex: calling confirm or sync call) + pub sdk_next_action: SdkNextAction, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] @@ -4209,6 +4211,8 @@ pub enum NextActionCall { Confirm, /// The next action call is sync Sync, + /// The next action call is Complete Authorize + CompleteAuthorize, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 667bc90fe45f..574902305113 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -7819,6 +7819,168 @@ impl Default for super::settings::RequiredFields { ]), }, ), + ( + enums::PaymentMethodType::Paypal, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Braintree, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ]), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new( + ), + common: HashMap::from( + [ + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ] + ), + } + ), + ]), + }, + ), ])), ), ( diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index 1eef83b95fb1..f4cbfd33a144 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -308,6 +308,9 @@ impl payments::PaypalSessionTokenResponse { session_token: item.response.client_token.value.expose(), connector: "braintree".to_string(), + sdk_next_action: payments::SdkNextAction { + next_action: payments::NextActionCall::Confirm, + }, }, )), }), diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 12e5c2f95012..2ee4be473525 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -677,14 +677,15 @@ fn create_paypal_sdk_session_token( expected_format: "paypal_sdk_metadata_format".to_string(), })?; - // [TODO: Add Dynamic fields support for payal sdk] - Ok(types::PaymentsSessionRouterData { response: Ok(types::PaymentsResponseData::SessionResponse { session_token: payment_types::SessionToken::Paypal(Box::new( payment_types::PaypalSessionTokenResponse { connector: connector.connector_name.to_string(), session_token: paypal_sdk_data.data.client_id, + sdk_next_action: payment_types::SdkNextAction { + next_action: payment_types::NextActionCall::CompleteAuthorize, + }, }, )), }), From 2297ebf1281bafb979af519d7451da3cec982fdd Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 13:59:43 +0000 Subject: [PATCH 08/18] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index ed7373990f2c..447f4303f866 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -11637,7 +11637,8 @@ "type": "string", "enum": [ "confirm", - "sync" + "sync", + "complete_authorize" ] }, "NextActionData": { @@ -16654,7 +16655,8 @@ "type": "object", "required": [ "connector", - "session_token" + "session_token", + "sdk_next_action" ], "properties": { "connector": { @@ -16664,6 +16666,9 @@ "session_token": { "type": "string", "description": "The session token for PayPal" + }, + "sdk_next_action": { + "$ref": "#/components/schemas/SdkNextAction" } } }, From ce6843d43ef6e9bb747c385fe3a7a56a20ea0ec9 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Tue, 21 May 2024 19:34:00 +0530 Subject: [PATCH 09/18] handle conflicts --- crates/router/src/routes/app.rs | 4 ---- crates/router/src/routes/payments.rs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 8099c0972830..c00c0e17ff82 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -393,10 +393,6 @@ impl Payments { web::resource("/{payment_id}/complete_authorize") .route(web::post().to(payments_complete_authorize)), ) - .service( - web::resource("/{payment_id}/complete_authorize") - .route(web::post().to(payments_complete_authorize_flow)), - ) .service( web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments_incremental_authorization)), ) diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 81e2c068ba1b..f28cad89004f 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -5,7 +5,7 @@ use crate::{ pub mod helpers; use actix_web::{web, Responder}; -use api_models::payments::{Address, HeaderPayload, PhoneDetails}; +use api_models::payments::HeaderPayload; use error_stack::report; use masking::PeekInterface; use router_env::{env, instrument, tracing, types, Flow}; From 1ff186abca43abe6331316ebbeef5152bc5b1aa0 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Wed, 22 May 2024 17:34:18 +0530 Subject: [PATCH 10/18] add invoke_sdk_client as next_action in confirm response --- crates/api_models/src/payments.rs | 23 +++++- .../stripe/payment_intents/types.rs | 8 ++ .../stripe/setup_intents/types.rs | 8 ++ crates/router/src/connector/paypal.rs | 17 +++-- .../src/connector/paypal/transformers.rs | 74 +++++++++++++++++-- crates/router/src/core/payments.rs | 1 + .../router/src/core/payments/transformers.rs | 28 +++++++ 7 files changed, 142 insertions(+), 17 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index d15429b1c620..ec8d9598d4b4 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2922,13 +2922,17 @@ pub enum NextActionType { #[serde(tag = "type", rename_all = "snake_case")] pub enum NextActionData { /// Contains the url for redirection flow - RedirectToUrl { redirect_to_url: String }, + RedirectToUrl { + redirect_to_url: String, + }, /// Informs the next steps for bank transfer and also contains the charges details (ex: amount received, amount charged etc) DisplayBankTransferInformation { bank_transfer_steps_and_charges_details: BankTransferNextStepsData, }, /// Contains third party sdk session token response - ThirdPartySdkSessionToken { session_token: Option }, + ThirdPartySdkSessionToken { + session_token: Option, + }, /// Contains url for Qr code image, this qr code has to be shown in sdk QrCodeInformation { #[schema(value_type = String)] @@ -2950,7 +2954,12 @@ pub enum NextActionData { display_to_timestamp: Option, }, /// Contains the information regarding three_ds_method_data submission, three_ds authentication, and authorization flows - ThreeDsInvoke { three_ds_data: ThreeDsData }, + ThreeDsInvoke { + three_ds_data: ThreeDsData, + }, + InvokeSdkClient { + next_action_data: NextActionCall, + }, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] @@ -3009,6 +3018,12 @@ pub enum QrCodeInformation { }, } +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct PaypalSdkNextActionData { + pub next_action: NextActionCall, +} + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] pub struct BankTransferNextStepsData { /// The instructions for performing a bank transfer @@ -4213,7 +4228,7 @@ pub struct SdkNextAction { pub next_action: NextActionCall, } -#[derive(Debug, Eq, PartialEq, serde::Serialize, Clone, ToSchema)] +#[derive(Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(rename_all = "snake_case")] pub enum NextActionCall { /// The next action call is confirm diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index d615acff1caf..c278fb6d569a 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -823,6 +823,9 @@ pub enum StripeNextAction { display_from_timestamp: i128, display_to_timestamp: Option, }, + InvokeSdkClient { + next_action: payments::NextActionCall, + }, } pub(crate) fn into_stripe_next_action( @@ -871,6 +874,11 @@ pub(crate) fn into_stripe_next_action( url: None, }, }, + payments::NextActionData::InvokeSdkClient { next_action_data } => { + StripeNextAction::InvokeSdkClient { + next_action: next_action_data, + } + } }) } diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 03335d427228..2060da7b3f24 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -389,6 +389,9 @@ pub enum StripeNextAction { display_from_timestamp: i128, display_to_timestamp: Option, }, + InvokeSdkClient { + next_action: payments::NextActionCall, + }, } pub(crate) fn into_stripe_next_action( @@ -437,6 +440,11 @@ pub(crate) fn into_stripe_next_action( url: None, }, }, + payments::NextActionData::InvokeSdkClient { next_action_data } => { + StripeNextAction::InvokeSdkClient { + next_action: next_action_data, + } + } }) } diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index cf81fec63417..97768a59df57 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -34,7 +34,7 @@ use crate::{ self, api::{self, CompleteAuthorize, ConnectorCommon, ConnectorCommonExt, VerifyWebhookSource}, storage::enums as storage_enums, - transformers::ForeignFrom, + transformers::{ForeignFrom, ForeignTryFrom}, ConnectorAuthType, ErrorResponse, Response, }, utils::BytesExt, @@ -629,7 +629,7 @@ impl ConnectorIntegration { event_builder.map(|i| i.set_response_body(&response)); @@ -644,11 +644,14 @@ impl ConnectorIntegration { event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) + types::RouterData::foreign_try_from(( + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + payment_method_data, + )) } PaypalAuthResponse::PaypalThreeDsResponse(response) => { event_builder.map(|i| i.set_response_body(&response)); diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index e3aed898f96d..7c803c1fe81d 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -18,7 +18,9 @@ use crate::{ core::errors, services, types::{ - self, api, domain, storage::enums as storage_enums, transformers::ForeignFrom, + self, api, domain, + storage::enums as storage_enums, + transformers::{ForeignFrom, ForeignTryFrom}, ConnectorAuthType, VerifyWebhookSourceResponseData, }, }; @@ -1059,6 +1061,7 @@ pub struct PaypalMeta { pub authorize_id: Option, pub capture_id: Option, pub psync_flow: PaypalPaymentIntent, + pub next_action: Option, } fn get_id_based_on_intent( @@ -1111,7 +1114,8 @@ impl serde_json::json!(PaypalMeta { authorize_id: None, capture_id: Some(id), - psync_flow: item.response.intent.clone() + psync_flow: item.response.intent.clone(), + next_action: None, }), types::ResponseId::ConnectorTransactionId(item.response.id.clone()), ), @@ -1120,7 +1124,8 @@ impl serde_json::json!(PaypalMeta { authorize_id: Some(id), capture_id: None, - psync_flow: item.response.intent.clone() + psync_flow: item.response.intent.clone(), + next_action: None, }), types::ResponseId::ConnectorTransactionId(item.response.id.clone()), ), @@ -1248,10 +1253,65 @@ impl let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, - psync_flow: item.response.intent + psync_flow: item.response.intent, + next_action: None, }); let purchase_units = item.response.purchase_units.first(); + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data: Some(services::RedirectForm::from(( + link.ok_or(errors::ConnectorError::ResponseDeserializationFailed)?, + services::Method::Get, + ))), + mandate_reference: None, + connector_metadata: Some(connector_meta), + network_txn_id: None, + connector_response_reference_id: Some( + purchase_units.map_or(item.response.id, |item| item.invoice_id.clone()), + ), + incremental_authorization_allowed: None, + }), + ..item.data + }) + } +} + +impl + ForeignTryFrom<( + types::ResponseRouterData, + domain::PaymentMethodData, + )> for types::RouterData +{ + type Error = error_stack::Report; + fn foreign_try_from( + (item, payment_method_data): ( + types::ResponseRouterData, + domain::PaymentMethodData, + ), + ) -> Result { + let status = storage_enums::AttemptStatus::foreign_from(( + item.response.clone().status, + item.response.intent.clone(), + )); + let link = get_redirect_url(item.response.links.clone())?; + let next_action = + if let domain::PaymentMethodData::Wallet(domain::WalletData::PaypalSdk(_)) = + payment_method_data + { + Some(api_models::payments::NextActionCall::CompleteAuthorize) + } else { + None + }; + let connector_meta = serde_json::json!(PaypalMeta { + authorize_id: None, + capture_id: None, + psync_flow: item.response.intent, + next_action, + }); + let purchase_units = item.response.purchase_units.first(); Ok(Self { status, response: Ok(types::PaymentsResponseData::TransactionResponse { @@ -1332,7 +1392,8 @@ impl let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, - psync_flow: PaypalPaymentIntent::Authenticate // when there is no capture or auth id present + psync_flow: PaypalPaymentIntent::Authenticate, // when there is no capture or auth id present + next_action: None, }); let status = storage_enums::AttemptStatus::foreign_from(( @@ -1749,7 +1810,8 @@ impl TryFrom> connector_metadata: Some(serde_json::json!(PaypalMeta { authorize_id: connector_payment_id.authorize_id, capture_id: Some(item.response.id.clone()), - psync_flow: PaypalPaymentIntent::Capture + psync_flow: PaypalPaymentIntent::Capture, + next_action: None, })), network_txn_id: None, connector_response_reference_id: item diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index ff48d181a77a..3cb94a33af62 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1011,6 +1011,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { api_models::payments::NextActionData::DisplayVoucherInformation{ .. } => None, api_models::payments::NextActionData::WaitScreenInformation{..} => None, api_models::payments::NextActionData::ThreeDsInvoke{..} => None, + api_models::payments::NextActionData::InvokeSdkClient{..} => None, }) .ok_or(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index bcc71f82bcc0..4050f76e4948 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -535,6 +535,8 @@ where let next_action_containing_qr_code_url = qr_code_next_steps_check(payment_attempt.clone())?; + let papal_sdk_next_action = paypal_sdk_next_steps_check(payment_attempt.clone())?; + let next_action_containing_wait_screen = wait_screen_next_steps_check(payment_attempt.clone())?; @@ -543,6 +545,7 @@ where || next_action_voucher.is_some() || next_action_containing_qr_code_url.is_some() || next_action_containing_wait_screen.is_some() + || papal_sdk_next_action.is_some() || payment_data.authentication.is_some() { next_action_response = bank_transfer_next_steps @@ -559,6 +562,9 @@ where .or(next_action_containing_qr_code_url.map(|qr_code_data| { api_models::payments::NextActionData::foreign_from(qr_code_data) })) + .or(papal_sdk_next_action.map(|paypal_next_action_data| { + api_models::payments::NextActionData::foreign_from(paypal_next_action_data) + })) .or(next_action_containing_wait_screen.map(|wait_screen_data| { api_models::payments::NextActionData::WaitScreenInformation { display_from_timestamp: wait_screen_data.display_from_timestamp, @@ -845,6 +851,18 @@ pub fn qr_code_next_steps_check( let qr_code_instructions = qr_code_steps.transpose().ok().flatten(); Ok(qr_code_instructions) } +pub fn paypal_sdk_next_steps_check( + payment_attempt: storage::PaymentAttempt, +) -> RouterResult> { + let paypal_connector_metadata: Option< + Result, + > = payment_attempt + .connector_metadata + .map(|metadata| metadata.parse_value("PaypalSdkNextActionData")); + + let paypal_next_steps = paypal_connector_metadata.transpose().ok().flatten(); + Ok(paypal_next_steps) +} pub fn wait_screen_next_steps_check( payment_attempt: storage::PaymentAttempt, @@ -1062,6 +1080,16 @@ impl ForeignFrom for api_models::paymen } } +impl ForeignFrom + for api_models::payments::NextActionData +{ + fn foreign_from(paypal_sdk_data: api_models::payments::PaypalSdkNextActionData) -> Self { + Self::InvokeSdkClient { + next_action_data: paypal_sdk_data.next_action, + } + } +} + #[derive(Clone)] pub struct PaymentAdditionalData<'a, F> where From 5f3b8d99777aceb5e56bfa22af04c9c4bae81925 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 12:06:30 +0000 Subject: [PATCH 11/18] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 447f4303f866..a712ed6e21ff 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -11794,6 +11794,24 @@ ] } } + }, + { + "type": "object", + "required": [ + "next_action_data", + "type" + ], + "properties": { + "next_action_data": { + "$ref": "#/components/schemas/NextActionCall" + }, + "type": { + "type": "string", + "enum": [ + "invoke_sdk_client" + ] + } + } } ], "discriminator": { From cb70f9ab7d53cb53d26f2887715c89baba7e4b10 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Thu, 23 May 2024 13:32:13 +0530 Subject: [PATCH 12/18] chore: refactor paypal sdk flow --- .../src/connector/paypal/transformers.rs | 85 ++++++++++--------- .../src/core/payments/flows/session_flow.rs | 2 +- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 7c803c1fe81d..1a1428e0b365 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -389,28 +389,33 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP let paypal_auth: PaypalAuthType = PaypalAuthType::try_from(&item.router_data.connector_auth_type)?; let payee = get_payee(&paypal_auth); + + let amount = OrderRequestAmount::from(item); + + let intent = if item.router_data.request.is_auto_capture()? { + PaypalPaymentIntent::Capture + } else { + PaypalPaymentIntent::Authorize + }; + + let connector_request_reference_id = + item.router_data.connector_request_reference_id.clone(); + + let shipping_address = ShippingAddress::try_from(item)?; + let item_details = vec![ItemDetails::from(item)]; + + let purchase_units = vec![PurchaseUnitRequest { + reference_id: Some(connector_req_reference_id.clone()), + custom_id: Some(connector_req_reference_id.clone()), + invoice_id: Some(connector_req_reference_id), + amount, + payee, + shipping: Some(shipping_address), + items: item_details, + }]; + match item.router_data.request.payment_method_data { domain::PaymentMethodData::Card(ref ccard) => { - let intent = if item.router_data.request.is_auto_capture()? { - PaypalPaymentIntent::Capture - } else { - PaypalPaymentIntent::Authorize - }; - let amount = OrderRequestAmount::from(item); - let connector_request_reference_id = - item.router_data.connector_request_reference_id.clone(); - let shipping_address = ShippingAddress::try_from(item)?; - let item_details = vec![ItemDetails::from(item)]; - - let purchase_units = vec![PurchaseUnitRequest { - reference_id: Some(connector_request_reference_id.clone()), - custom_id: Some(connector_request_reference_id.clone()), - invoice_id: Some(connector_request_reference_id), - amount, - payee, - shipping: Some(shipping_address), - items: item_details, - }]; let card = item.router_data.request.get_card()?; let expiry = Some(card.get_expiry_date_as_yyyymm("-")); @@ -442,28 +447,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP }) } domain::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { - domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) => { - let intent = if item.router_data.request.is_auto_capture()? { - PaypalPaymentIntent::Capture - } else { - PaypalPaymentIntent::Authorize - }; - let amount = OrderRequestAmount::from(item); - - let connector_req_reference_id = - item.router_data.connector_request_reference_id.clone(); - let shipping_address = ShippingAddress::try_from(item)?; - let item_details = vec![ItemDetails::from(item)]; - - let purchase_units = vec![PurchaseUnitRequest { - reference_id: Some(connector_req_reference_id.clone()), - custom_id: Some(connector_req_reference_id.clone()), - invoice_id: Some(connector_req_reference_id), - amount, - payee, - shipping: Some(shipping_address), - items: item_details, - }]; + domain::WalletData::PaypalRedirect(_) => { let payment_source = Some(PaymentSourceItem::Paypal(PaypalRedirectionRequest { experience_context: ContextStruct { @@ -488,6 +472,23 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP payment_source, }) } + domain::WalletData::PaypalSdk(_) => { + let payment_source = + Some(PaymentSourceItem::Paypal(PaypalRedirectionRequest { + experience_context: ContextStruct { + return_url: None, + cancel_url: None, + shipping_preference: ShippingPreference::GetFromFile, + user_action: Some(UserAction::PayNow), + }, + })); + + Ok(Self { + intent, + purchase_units, + payment_source, + }) + } domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 2ee4be473525..ac3f275194bb 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -684,7 +684,7 @@ fn create_paypal_sdk_session_token( connector: connector.connector_name.to_string(), session_token: paypal_sdk_data.data.client_id, sdk_next_action: payment_types::SdkNextAction { - next_action: payment_types::NextActionCall::CompleteAuthorize, + next_action: payment_types::NextActionCall::Confirm, }, }, )), From b90ce54f157abba54b88e6b4235a12bf4582ad10 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Thu, 23 May 2024 16:34:24 +0530 Subject: [PATCH 13/18] handle connector meta based onpayment experience in psync --- crates/router/src/connector/paypal.rs | 13 ++-- .../src/connector/paypal/transformers.rs | 78 ++++++++++--------- .../router/src/core/payments/transformers.rs | 1 + crates/router/src/types.rs | 1 + 4 files changed, 50 insertions(+), 43 deletions(-) diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 97768a59df57..e2e28e7cb6e6 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -1034,11 +1034,14 @@ impl ConnectorIntegration> for PaypalP let item_details = vec![ItemDetails::from(item)]; let purchase_units = vec![PurchaseUnitRequest { - reference_id: Some(connector_req_reference_id.clone()), - custom_id: Some(connector_req_reference_id.clone()), - invoice_id: Some(connector_req_reference_id), + reference_id: Some(connector_request_reference_id.clone()), + custom_id: Some(connector_request_reference_id.clone()), + invoice_id: Some(connector_request_reference_id), amount, payee, shipping: Some(shipping_address), @@ -517,7 +517,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP ))?, }, domain::PaymentMethodData::BankRedirect(ref bank_redirection_data) => { - let intent = if item.router_data.request.is_auto_capture()? { + let bank_redirect_intent = if item.router_data.request.is_auto_capture()? { PaypalPaymentIntent::Capture } else { Err(errors::ConnectorError::FlowNotSupported { @@ -525,26 +525,12 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP connector: "Paypal".to_string(), })? }; - let amount = OrderRequestAmount::from(item); - let connector_req_reference_id = - item.router_data.connector_request_reference_id.clone(); - let shipping_address = ShippingAddress::try_from(item)?; - let item_details = vec![ItemDetails::from(item)]; - - let purchase_units = vec![PurchaseUnitRequest { - reference_id: Some(connector_req_reference_id.clone()), - custom_id: Some(connector_req_reference_id.clone()), - invoice_id: Some(connector_req_reference_id), - amount, - payee, - shipping: Some(shipping_address), - items: item_details, - }]; + let payment_source = Some(get_payment_source(item.router_data, bank_redirection_data)?); Ok(Self { - intent, + intent: bank_redirect_intent, purchase_units, payment_source, }) @@ -1187,23 +1173,27 @@ fn get_redirect_url( } impl - TryFrom< + ForeignTryFrom<( types::ResponseRouterData< F, PaypalSyncResponse, types::PaymentsSyncData, types::PaymentsResponseData, >, - > for types::RouterData + Option, + )> for types::RouterData { type Error = error_stack::Report; - fn try_from( - item: types::ResponseRouterData< - F, - PaypalSyncResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, - >, + fn foreign_try_from( + (item, payment_experience): ( + types::ResponseRouterData< + F, + PaypalSyncResponse, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, + Option, + ), ) -> Result { match item.response { PaypalSyncResponse::PaypalOrdersSyncResponse(response) => { @@ -1213,13 +1203,14 @@ impl http_code: item.http_code, }) } - PaypalSyncResponse::PaypalRedirectSyncResponse(response) => { - Self::try_from(types::ResponseRouterData { + PaypalSyncResponse::PaypalRedirectSyncResponse(response) => Self::foreign_try_from(( + types::ResponseRouterData { response, data: item.data, http_code: item.http_code, - }) - } + }, + payment_experience, + )), PaypalSyncResponse::PaypalPaymentsSyncResponse(response) => { Self::try_from(types::ResponseRouterData { response, @@ -1239,23 +1230,34 @@ impl } impl - TryFrom> - for types::RouterData + ForeignTryFrom<( + types::ResponseRouterData, + Option, + )> for types::RouterData { type Error = error_stack::Report; - fn try_from( - item: types::ResponseRouterData, + fn foreign_try_from( + (item, payment_experience): ( + types::ResponseRouterData, + Option, + ), ) -> Result { let status = storage_enums::AttemptStatus::foreign_from(( item.response.clone().status, item.response.intent.clone(), )); let link = get_redirect_url(item.response.links.clone())?; + let next_action = + if let Some(common_enums::PaymentExperience::InvokeSdkClient) = payment_experience { + Some(api_models::payments::NextActionCall::CompleteAuthorize) + } else { + None + }; let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, psync_flow: item.response.intent, - next_action: None, + next_action, }); let purchase_units = item.response.purchase_units.first(); Ok(Self { diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 4050f76e4948..0952d407fc38 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1262,6 +1262,7 @@ impl TryFrom> for types::PaymentsSyncData }, payment_method_type: payment_data.payment_attempt.payment_method_type, currency: payment_data.currency, + payment_experience: payment_data.payment_attempt.payment_experience, }) } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 0afef760517b..ef0ea57d47ff 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -470,6 +470,7 @@ pub struct PaymentsSyncData { pub mandate_id: Option, pub payment_method_type: Option, pub currency: storage_enums::Currency, + pub payment_experience: Option, } #[derive(Debug, Default, Clone)] From 214d5ce7c6b953d1b8d2921f8c6a5c3101373dcc Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Mon, 27 May 2024 01:58:18 +0530 Subject: [PATCH 14/18] chore: address cargo errors and refactor api contract --- crates/api_models/src/payments.rs | 7 +++--- .../stripe/payment_intents/types.rs | 6 ++--- .../stripe/setup_intents/types.rs | 6 ++--- .../src/connector/paypal/transformers.rs | 1 + .../router/src/core/payments/transformers.rs | 25 ++++++------------- crates/router/tests/connectors/bambora.rs | 2 ++ crates/router/tests/connectors/forte.rs | 1 + crates/router/tests/connectors/nexinets.rs | 1 + crates/router/tests/connectors/paypal.rs | 2 ++ crates/router/tests/connectors/utils.rs | 1 + crates/router/tests/connectors/zen.rs | 2 ++ 11 files changed, 25 insertions(+), 29 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 13b5e541c8d8..1dc82a2023e1 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2975,7 +2975,7 @@ pub enum NextActionData { three_ds_data: ThreeDsData, }, InvokeSdkClient { - next_action_data: NextActionCall, + next_action_data: SdkNextActionData, }, } @@ -3039,9 +3039,9 @@ pub enum QrCodeInformation { }, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] #[serde(rename_all = "snake_case")] -pub struct PaypalSdkNextActionData { +pub struct SdkNextActionData { pub next_action: NextActionCall, } @@ -4431,7 +4431,6 @@ pub struct PaymentsCompleteAuthorizeRequest { pub payment_id: String, /// The shipping address for the payment pub shipping: Option
, - /// Client Secret #[schema(value_type = String)] pub client_secret: Secret, diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index c278fb6d569a..5e8c9ea08c0f 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -824,7 +824,7 @@ pub enum StripeNextAction { display_to_timestamp: Option, }, InvokeSdkClient { - next_action: payments::NextActionCall, + next_action_data: payments::SdkNextActionData, }, } @@ -875,9 +875,7 @@ pub(crate) fn into_stripe_next_action( }, }, payments::NextActionData::InvokeSdkClient { next_action_data } => { - StripeNextAction::InvokeSdkClient { - next_action: next_action_data, - } + StripeNextAction::InvokeSdkClient { next_action_data } } }) } diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 2060da7b3f24..bbcafb65e9ff 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -390,7 +390,7 @@ pub enum StripeNextAction { display_to_timestamp: Option, }, InvokeSdkClient { - next_action: payments::NextActionCall, + next_action_data: payments::SdkNextActionData, }, } @@ -441,9 +441,7 @@ pub(crate) fn into_stripe_next_action( }, }, payments::NextActionData::InvokeSdkClient { next_action_data } => { - StripeNextAction::InvokeSdkClient { - next_action: next_action_data, - } + StripeNextAction::InvokeSdkClient { next_action_data } } }) } diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index a0967b3555b0..0f7850b320cf 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -1276,6 +1276,7 @@ impl purchase_units.map_or(item.response.id, |item| item.invoice_id.clone()), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 42a24278ccad..738bf5afbe66 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -567,7 +567,9 @@ where api_models::payments::NextActionData::foreign_from(qr_code_data) })) .or(papal_sdk_next_action.map(|paypal_next_action_data| { - api_models::payments::NextActionData::foreign_from(paypal_next_action_data) + api_models::payments::NextActionData::InvokeSdkClient{ + next_action_data: paypal_next_action_data + } })) .or(next_action_containing_wait_screen.map(|wait_screen_data| { api_models::payments::NextActionData::WaitScreenInformation { @@ -883,12 +885,11 @@ pub fn qr_code_next_steps_check( } pub fn paypal_sdk_next_steps_check( payment_attempt: storage::PaymentAttempt, -) -> RouterResult> { - let paypal_connector_metadata: Option< - Result, - > = payment_attempt - .connector_metadata - .map(|metadata| metadata.parse_value("PaypalSdkNextActionData")); +) -> RouterResult> { + let paypal_connector_metadata: Option> = + payment_attempt + .connector_metadata + .map(|metadata| metadata.parse_value("SdkNextActionData")); let paypal_next_steps = paypal_connector_metadata.transpose().ok().flatten(); Ok(paypal_next_steps) @@ -1110,16 +1111,6 @@ impl ForeignFrom for api_models::paymen } } -impl ForeignFrom - for api_models::payments::NextActionData -{ - fn foreign_from(paypal_sdk_data: api_models::payments::PaypalSdkNextActionData) -> Self { - Self::InvokeSdkClient { - next_action_data: paypal_sdk_data.next_action, - } - } -} - #[derive(Clone)] pub struct PaymentAdditionalData<'a, F> where diff --git a/crates/router/tests/connectors/bambora.rs b/crates/router/tests/connectors/bambora.rs index 3115c1143d76..d0bc77e75005 100644 --- a/crates/router/tests/connectors/bambora.rs +++ b/crates/router/tests/connectors/bambora.rs @@ -110,6 +110,7 @@ async fn should_sync_authorized_payment() { connector_meta: None, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), None, ) @@ -226,6 +227,7 @@ async fn should_sync_auto_captured_payment() { connector_meta: None, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), None, ) diff --git a/crates/router/tests/connectors/forte.rs b/crates/router/tests/connectors/forte.rs index d690524bd12b..11153aa70885 100644 --- a/crates/router/tests/connectors/forte.rs +++ b/crates/router/tests/connectors/forte.rs @@ -159,6 +159,7 @@ async fn should_sync_authorized_payment() { mandate_id: None, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), get_default_payment_info(), ) diff --git a/crates/router/tests/connectors/nexinets.rs b/crates/router/tests/connectors/nexinets.rs index cdf94113c5a3..c6a14ac202fe 100644 --- a/crates/router/tests/connectors/nexinets.rs +++ b/crates/router/tests/connectors/nexinets.rs @@ -126,6 +126,7 @@ async fn should_sync_authorized_payment() { mandate_id: None, payment_method_type: None, currency: enums::Currency::EUR, + payment_experience: None, }), None, ) diff --git a/crates/router/tests/connectors/paypal.rs b/crates/router/tests/connectors/paypal.rs index 704aeca59365..b53cf71acfdb 100644 --- a/crates/router/tests/connectors/paypal.rs +++ b/crates/router/tests/connectors/paypal.rs @@ -143,6 +143,7 @@ async fn should_sync_authorized_payment() { connector_meta, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), get_default_payment_info(), ) @@ -341,6 +342,7 @@ async fn should_sync_auto_captured_payment() { connector_meta, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), get_default_payment_info(), ) diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 6718611b0c8d..9955cb77df73 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -1002,6 +1002,7 @@ impl Default for PaymentSyncType { connector_meta: None, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }; Self(data) } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index b11b78025d0b..552edcaaf6f1 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -105,6 +105,7 @@ async fn should_sync_authorized_payment() { mandate_id: None, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), None, ) @@ -221,6 +222,7 @@ async fn should_sync_auto_captured_payment() { mandate_id: None, payment_method_type: None, currency: enums::Currency::USD, + payment_experience: None, }), None, ) From 1522985d2f6085b9224a58662d23d0318710cded Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Mon, 27 May 2024 02:43:56 +0530 Subject: [PATCH 15/18] chore: add open api spec --- crates/openapi/src/openapi.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 8830c6899376..c67ea2d27465 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -367,6 +367,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplepaySessionTokenResponse, api_models::payments::SdkNextAction, api_models::payments::NextActionCall, + api_models::payments::SdkNextActionData, api_models::payments::SamsungPayWalletData, api_models::payments::WeChatPay, api_models::payments::GpayTokenizationData, From f670bd021bbe0fa71423a3288a30cb3f3fe13406 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Mon, 27 May 2024 14:52:28 +0530 Subject: [PATCH 16/18] add comments and logs --- crates/router/src/configs/defaults.rs | 2 ++ crates/router/src/connector/paypal/transformers.rs | 4 ++++ crates/router/src/core/payments/transformers.rs | 10 +++++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index f62045c47506..0405f3180711 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -7915,6 +7915,8 @@ impl Default for super::settings::RequiredFields { }, ), ( + // Added shipping fields for the SDK flow to accept it from wallet directly, + // this won't show up in SDK in payment's sheet but will be used in the background enums::PaymentMethodType::Paypal, ConnectorFields { fields: HashMap::from([ diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 0f7850b320cf..d240c1b05e0e 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -1248,6 +1248,8 @@ impl item.response.intent.clone(), )); let link = get_redirect_url(item.response.links.clone())?; + + // For Paypal SDK flow, we need to trigger SDK client and then complete authorize let next_action = if let Some(common_enums::PaymentExperience::InvokeSdkClient) = payment_experience { Some(api_models::payments::NextActionCall::CompleteAuthorize) @@ -1301,6 +1303,8 @@ impl item.response.intent.clone(), )); let link = get_redirect_url(item.response.links.clone())?; + + // For Paypal SDK flow, we need to trigger SDK client and then complete authorize let next_action = if let domain::PaymentMethodData::Wallet(domain::WalletData::PaypalSdk(_)) = payment_method_data diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 738bf5afbe66..7f6b0cc1f61f 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -887,9 +887,13 @@ pub fn paypal_sdk_next_steps_check( payment_attempt: storage::PaymentAttempt, ) -> RouterResult> { let paypal_connector_metadata: Option> = - payment_attempt - .connector_metadata - .map(|metadata| metadata.parse_value("SdkNextActionData")); + payment_attempt.connector_metadata.map(|metadata| { + metadata.parse_value("SdkNextActionData").map_err(|_| { + crate::logger::warn!( + "SdkNextActionData parsing failed for paypal_connector_metadata" + ) + }) + }); let paypal_next_steps = paypal_connector_metadata.transpose().ok().flatten(); Ok(paypal_next_steps) From a7b8b67f398a4d8d7c102e4da6b1dad206f4b746 Mon Sep 17 00:00:00 2001 From: Samraat Bansal Date: Mon, 27 May 2024 15:22:42 +0530 Subject: [PATCH 17/18] chore: handle open api spec --- crates/api_models/src/payments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 1dc82a2023e1..5f9618738b2b 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3039,7 +3039,7 @@ pub enum QrCodeInformation { }, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, ToSchema)] #[serde(rename_all = "snake_case")] pub struct SdkNextActionData { pub next_action: NextActionCall, From f67f5eceaa0fbf610ceb3c838c4ae99861de1b9e Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 09:55:00 +0000 Subject: [PATCH 18/18] docs(openapi): re-generate OpenAPI specification --- openapi/openapi_spec.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 98e49e7e2bdd..b87b516de28f 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -11826,7 +11826,7 @@ ], "properties": { "next_action_data": { - "$ref": "#/components/schemas/NextActionCall" + "$ref": "#/components/schemas/SdkNextActionData" }, "type": { "type": "string", @@ -18058,6 +18058,17 @@ } } }, + "SdkNextActionData": { + "type": "object", + "required": [ + "next_action" + ], + "properties": { + "next_action": { + "$ref": "#/components/schemas/NextActionCall" + } + } + }, "SecretInfoToInitiateSdk": { "type": "object", "required": [