diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 5a66f45033..23043d0e0a 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -40,6 +40,7 @@ pub enum AttemptStatus { Failure, PaymentMethodAwaited, ConfirmationAwaited, + DeviceDataCollectionPending, } #[derive( @@ -767,9 +768,10 @@ impl From for IntentStatus { AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod, AttemptStatus::Authorized => Self::RequiresCapture, - AttemptStatus::AuthenticationPending => Self::RequiresCustomerAction, + AttemptStatus::AuthenticationPending | AttemptStatus::DeviceDataCollectionPending => { + Self::RequiresCustomerAction + } AttemptStatus::Unresolved => Self::RequiresMerchantAction, - AttemptStatus::PartialCharged | AttemptStatus::Started | AttemptStatus::AuthenticationSuccessful diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 9b82f9f737..e5bffe72e1 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1222,6 +1222,8 @@ pub struct Metadata { #[schema(value_type = Object, example = r#"{ "city": "NY", "unit": "245" }"#)] #[serde(flatten)] pub data: pii::SecretSerdeValue, + /// Payload coming in request as a metadata field + pub payload: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] diff --git a/crates/router/src/connector/airwallex.rs b/crates/router/src/connector/airwallex.rs index f389c66a33..15dd12b916 100644 --- a/crates/router/src/connector/airwallex.rs +++ b/crates/router/src/connector/airwallex.rs @@ -88,7 +88,7 @@ impl ConnectorCommon for Airwallex { } impl api::Payment for Airwallex {} - +impl api::PaymentsCompleteAuthorize for Airwallex {} impl api::PreVerify for Airwallex {} impl ConnectorIntegration for Airwallex @@ -461,6 +461,95 @@ impl ConnectorIntegration for Airwallex +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + 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::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req + .request + .connector_transaction_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}api/v1/pa/payment_intents/{}/confirm_continue", + self.base_url(connectors), + connector_payment_id, + )) + } + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + ) -> CustomResult, errors::ConnectorError> { + let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?; + let req = utils::Encode::::encode_to_string_of_json( + &req_obj, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(req)) + } + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsComeplteAuthorizeType::get_url( + self, req, connectors, + )?) + .headers(types::PaymentsComeplteAuthorizeType::get_headers( + self, req, connectors, + )?) + .body(types::PaymentsComeplteAuthorizeType::get_request_body( + self, req, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + res: Response, + ) -> CustomResult { + let response: airwallex::AirwallexPaymentsResponse = res + .response + .parse_struct("AirwallexPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + impl api::PaymentCapture for Airwallex {} impl ConnectorIntegration for Airwallex @@ -906,3 +995,18 @@ impl api::IncomingWebhook for Airwallex { Ok(details.data.object) } } + +impl services::ConnectorRedirectResponse for Airwallex { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + action: services::PaymentAction, + ) -> CustomResult { + match action { + services::PaymentAction::PSync | services::PaymentAction::CompleteAuthorize => { + Ok(payments::CallConnectorAction::Trigger) + } + } + } +} diff --git a/crates/router/src/connector/airwallex/transformers.rs b/crates/router/src/connector/airwallex/transformers.rs index a126ff7a77..c71f2ead3a 100644 --- a/crates/router/src/connector/airwallex/transformers.rs +++ b/crates/router/src/connector/airwallex/transformers.rs @@ -110,7 +110,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for AirwallexPaymentsRequest { request_id: Uuid::new_v4().to_string(), payment_method, payment_method_options, - return_url: item.request.router_return_url.clone(), + return_url: item.request.complete_authorize_url.clone(), }) } } @@ -140,6 +140,39 @@ impl TryFrom, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub enum AirwallexThreeDsType { + #[default] + #[serde(rename = "3ds_continue")] + ThreeDSContinue, +} + +impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for AirwallexCompleteRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result { + Ok(Self { + request_id: Uuid::new_v4().to_string(), + three_ds: AirwallexThreeDsData { + acs_response: item.request.payload.clone().map(Secret::new), + }, + three_ds_type: AirwallexThreeDsType::ThreeDSContinue, + }) + } +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct AirwallexPaymentsCaptureRequest { // Unique ID to be sent for each transaction/operation request to the connector @@ -191,27 +224,43 @@ pub enum AirwallexPaymentStatus { Cancelled, } -impl From for enums::AttemptStatus { - fn from(item: AirwallexPaymentStatus) -> Self { - match item { - AirwallexPaymentStatus::Succeeded => Self::Charged, - AirwallexPaymentStatus::Failed => Self::Failure, - AirwallexPaymentStatus::Pending => Self::Pending, - AirwallexPaymentStatus::RequiresPaymentMethod => Self::PaymentMethodAwaited, - AirwallexPaymentStatus::RequiresCustomerAction => Self::AuthenticationPending, - AirwallexPaymentStatus::RequiresCapture => Self::Authorized, - AirwallexPaymentStatus::Cancelled => Self::Voided, - } +fn get_payment_status(response: &AirwallexPaymentsResponse) -> enums::AttemptStatus { + match response.status.clone() { + AirwallexPaymentStatus::Succeeded => enums::AttemptStatus::Charged, + AirwallexPaymentStatus::Failed => enums::AttemptStatus::Failure, + AirwallexPaymentStatus::Pending => enums::AttemptStatus::Pending, + AirwallexPaymentStatus::RequiresPaymentMethod => enums::AttemptStatus::PaymentMethodAwaited, + AirwallexPaymentStatus::RequiresCustomerAction => response.next_action.as_ref().map_or( + enums::AttemptStatus::AuthenticationPending, + |next_action| match next_action.stage { + AirwallexNextActionStage::WaitingDeviceDataCollection => { + enums::AttemptStatus::DeviceDataCollectionPending + } + AirwallexNextActionStage::WaitingUserInfoInput => { + enums::AttemptStatus::AuthenticationPending + } + }, + ), + AirwallexPaymentStatus::RequiresCapture => enums::AttemptStatus::Authorized, + AirwallexPaymentStatus::Cancelled => enums::AttemptStatus::Voided, } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AirwallexNextActionStage { + WaitingDeviceDataCollection, + WaitingUserInfoInput, +} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct AirwallexRedirectFormData { #[serde(rename = "JWT")] - jwt: String, + jwt: Option, #[serde(rename = "threeDSMethodData")] - three_ds_method_data: String, - token: String, + three_ds_method_data: Option, + token: Option, + provider: Option, + version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -219,6 +268,7 @@ pub struct AirwallexPaymentsNextAction { url: Url, method: services::Method, data: AirwallexRedirectFormData, + stage: AirwallexNextActionStage, } #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -232,11 +282,46 @@ pub struct AirwallexPaymentsResponse { next_action: Option, } +fn get_redirection_form( + response_url_data: AirwallexPaymentsNextAction, +) -> Option { + Some(services::RedirectForm { + endpoint: response_url_data.url.to_string(), + method: response_url_data.method, + form_fields: std::collections::HashMap::from([ + //Some form fields might be empty based on the authentication type by the connector + ( + "JWT".to_string(), + response_url_data.data.jwt.unwrap_or_default(), + ), + ( + "threeDSMethodData".to_string(), + response_url_data + .data + .three_ds_method_data + .unwrap_or_default(), + ), + ( + "token".to_string(), + response_url_data.data.token.unwrap_or_default(), + ), + ( + "provider".to_string(), + response_url_data.data.provider.unwrap_or_default(), + ), + ( + "version".to_string(), + response_url_data.data.version.unwrap_or_default(), + ), + ]), + }) +} + impl TryFrom> for types::RouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, @@ -245,23 +330,48 @@ impl types::PaymentsResponseData, >, ) -> Result { - let redirection_data = - item.response - .next_action - .map(|response_url_data| services::RedirectForm { - endpoint: response_url_data.url.to_string(), - method: response_url_data.method, - form_fields: std::collections::HashMap::from([ - ("JWT".to_string(), response_url_data.data.jwt), - ( - "threeDSMethodData".to_string(), - response_url_data.data.three_ds_method_data, - ), - ("token".to_string(), response_url_data.data.token), - ]), - }); + let (status, redirection_data) = item.response.next_action.clone().map_or( + // If no next action is there, map the status and set redirection form as None + (get_payment_status(&item.response), None), + |response_url_data| { + // If the connector sends a customer action response that is already under + // process from our end it can cause an infinite loop to break this this check + // is added and fail the payment + if matches!( + ( + response_url_data.stage.clone(), + item.data.status, + item.response.status.clone(), + ), + // If the connector sends waiting for DDC and our status is already DDC Pending + // that means we initiated the call to collect the data and now we expect a different response + ( + AirwallexNextActionStage::WaitingDeviceDataCollection, + enums::AttemptStatus::DeviceDataCollectionPending, + _ + ) + // If the connector sends waiting for Customer Action and our status is already Authenticaition Pending + // that means we initiated the call to authenticate and now we do not expect a requires_customer action + // it will start a loop + | ( + _, + enums::AttemptStatus::AuthenticationPending, + AirwallexPaymentStatus::RequiresCustomerAction, + ) + ) { + // Fail the payment for above conditions + (enums::AttemptStatus::AuthenticationFailed, None) + } else { + ( + //Build the redirect form and update the payment status + get_payment_status(&item.response), + get_redirection_form(response_url_data), + ) + } + }, + ); Ok(Self { - status: enums::AttemptStatus::from(item.response.status), + status, reference_id: Some(item.response.id.clone()), response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 7f41594a5f..2f501f0c6c 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6,6 +6,7 @@ pub mod transformers; use std::{fmt::Debug, marker::PhantomData, time::Instant}; +use api_models::payments::Metadata; use error_stack::{IntoReport, ResultExt}; use futures::future::join_all; use router_env::tracing; @@ -247,6 +248,14 @@ pub trait PaymentRedirectFlow: Sync { fn get_payment_action(&self) -> services::PaymentAction; + fn generate_response( + &self, + payments_response: api_models::payments::PaymentsResponse, + merchant_account: storage_models::merchant_account::MerchantAccount, + payment_id: String, + connector: String, + ) -> RouterResult; + #[allow(clippy::too_many_arguments)] async fn handle_payments_redirect_response( &self, @@ -290,13 +299,8 @@ pub trait PaymentRedirectFlow: Sync { .attach_printable("Failed to get the response in json"), }?; - let result = helpers::get_handle_response_url( - resource_id, - &merchant_account, - payments_response, - connector, - ) - .attach_printable("No redirection response")?; + let result = + self.generate_response(payments_response, merchant_account, resource_id, connector)?; Ok(services::ApplicationResponse::JsonForRedirection(result)) } @@ -317,6 +321,11 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { let payment_confirm_req = api::PaymentsRequest { payment_id: Some(req.resource_id.clone()), merchant_id: req.merchant_id.clone(), + metadata: Some(Metadata { + order_details: None, + data: masking::Secret::new("{}".into()), + payload: Some(req.json_payload.unwrap_or("{}".into()).into()), + }), ..Default::default() }; payments_core::( @@ -333,6 +342,47 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { fn get_payment_action(&self) -> services::PaymentAction { services::PaymentAction::CompleteAuthorize } + + fn generate_response( + &self, + payments_response: api_models::payments::PaymentsResponse, + merchant_account: storage_models::merchant_account::MerchantAccount, + payment_id: String, + connector: String, + ) -> RouterResult { + // There might be multiple redirections needed for some flows + // If the status is requires customer action, then send the startpay url again + // The redirection data must have been provided and updated by the connector + match payments_response.status { + api_models::enums::IntentStatus::RequiresCustomerAction => { + let startpay_url = payments_response + .next_action + .and_then(|next_action| next_action.redirect_to_url) + .ok_or(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable( + "did not receive redirect to url when status is requires customer action", + )?; + Ok(api::RedirectionResponse { + return_url: String::new(), + params: vec![], + return_url_with_query_params: startpay_url, + http_method: "GET".to_string(), + headers: vec![], + }) + } + // If the status is terminal status, then redirect to merchant return url to provide status + api_models::enums::IntentStatus::Succeeded + | api_models::enums::IntentStatus::Failed + | api_models::enums::IntentStatus::Cancelled | api_models::enums::IntentStatus::RequiresCapture=> helpers::get_handle_response_url( + payment_id, + &merchant_account, + payments_response, + connector, + ), + _ => Err(errors::ApiErrorResponse::InternalServerError).into_report().attach_printable_lazy(|| format!("Could not proceed with payment as payment status {} cannot be handled during redirection",payments_response.status))? + } + } } #[derive(Clone, Debug)] @@ -371,6 +421,21 @@ impl PaymentRedirectFlow for PaymentRedirectSync { .await } + fn generate_response( + &self, + payments_response: api_models::payments::PaymentsResponse, + merchant_account: storage_models::merchant_account::MerchantAccount, + payment_id: String, + connector: String, + ) -> RouterResult { + helpers::get_handle_response_url( + payment_id, + &merchant_account, + payments_response, + connector, + ) + } + fn get_payment_action(&self) -> services::PaymentAction { services::PaymentAction::PSync } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index f9682eb5ea..d083e55ec0 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -88,7 +88,6 @@ macro_rules! default_imp_for_complete_authorize{ default_imp_for_complete_authorize!( connector::Aci, connector::Adyen, - connector::Airwallex, connector::Applepay, connector::Authorizedotnet, connector::Bambora, @@ -131,7 +130,6 @@ macro_rules! default_imp_for_connector_redirect_response{ default_imp_for_connector_redirect_response!( connector::Aci, connector::Adyen, - connector::Airwallex, connector::Applepay, connector::Authorizedotnet, connector::Bambora, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index dc6c35e251..52ac3c6cd6 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -339,7 +339,7 @@ pub fn create_startpay_url( payment_intent: &storage::PaymentIntent, ) -> String { format!( - "{}/payments/start/{}/{}/{}", + "{}/payments/redirect/{}/{}/{}", server.base_url, payment_intent.payment_id, payment_intent.merchant_id, @@ -355,7 +355,7 @@ pub fn create_redirect_url( ) -> String { let creds_identifier_path = creds_identifier.map_or_else(String::new, |cd| format!("/{}", cd)); format!( - "{}/payments/{}/{}/response/{}", + "{}/payments/{}/{}/redirect/response/{}", router_base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name, ) + &creds_identifier_path } @@ -376,7 +376,7 @@ pub fn create_complete_authorize_url( connector_name: &String, ) -> String { format!( - "{}/payments/{}/{}/complete/{}", + "{}/payments/{}/{}/redirect/complete/{}", router_base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name ) } @@ -933,6 +933,7 @@ pub fn get_handle_response_url( connector: String, ) -> RouterResult { let payments_return_url = response.return_url.as_ref(); + let redirection_response = make_pg_redirect_response(payment_id, &response, connector); let return_url = make_merchant_url_with_response( 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 aa12ae95d3..75ae10fc11 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -2,6 +2,7 @@ use std::marker::PhantomData; use async_trait::async_trait; use error_stack::ResultExt; +use masking::ExposeOptionInterface; use router_derive::PaymentOperation; use router_env::{instrument, tracing}; @@ -45,7 +46,7 @@ impl GetTracker, api::PaymentsRequest> for Co let db = &*state.store; let merchant_id = &merchant_account.merchant_id; let storage_scheme = merchant_account.storage_scheme; - let (mut payment_intent, mut payment_attempt, currency, amount, connector_response); + let (mut payment_intent, mut payment_attempt, currency, amount); let payment_id = payment_id .get_payment_intent_id() @@ -154,7 +155,7 @@ impl GetTracker, api::PaymentsRequest> for Co ) .await?; - connector_response = db + let mut connector_response = db .find_connector_response_by_payment_id_merchant_id_attempt_id( &payment_attempt.payment_id, &payment_attempt.merchant_id, @@ -166,6 +167,13 @@ impl GetTracker, api::PaymentsRequest> for Co error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) })?; + connector_response.encoded_data = request.metadata.clone().and_then(|secret_metadata| { + secret_metadata + .payload + .expose_option() + .map(|exposed_payload| exposed_payload.to_string()) + }); + payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id); payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id); payment_intent.return_url = request.return_url.as_ref().map(|a| a.to_string()); diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 646e977e16..ff2857b8fc 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -318,17 +318,10 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let payment_method = payment_data.payment_attempt.payment_method; let browser_info = payment_data.payment_attempt.browser_info.clone(); - let (intent_status, attempt_status) = match payment_data.payment_attempt.authentication_type - { - Some(storage_enums::AuthenticationType::NoThreeDs) => ( - storage_enums::IntentStatus::Processing, - storage_enums::AttemptStatus::Pending, - ), - _ => ( - storage_enums::IntentStatus::RequiresCustomerAction, - storage_enums::AttemptStatus::AuthenticationPending, - ), - }; + let (intent_status, attempt_status) = ( + storage_enums::IntentStatus::Processing, + storage_enums::AttemptStatus::Pending, + ); let connector = payment_data.payment_attempt.connector.clone(); let straight_through_algorithm = payment_data diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 1e41d6d80e..47fd448ebd 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1,6 +1,6 @@ use std::{fmt::Debug, marker::PhantomData}; -use error_stack::ResultExt; +use error_stack::{IntoReport, ResultExt}; use router_env::{instrument, tracing}; use super::{flows::Feature, PaymentAddress, PaymentData}; @@ -681,6 +681,14 @@ impl TryFrom> for types::CompleteAuthoriz .change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "browser_info", })?; + + let json_payload = payment_data + .connector_response + .encoded_data + .map(serde_json::to_value) + .transpose() + .into_report() + .change_context(errors::ApiErrorResponse::InternalServerError)?; Ok(Self { setup_future_usage: payment_data.payment_intent.setup_future_usage, mandate_id: payment_data.mandate_id.clone(), @@ -695,6 +703,7 @@ impl TryFrom> for types::CompleteAuthoriz email: payment_data.email, payment_method_data: payment_data.payment_method_data, connector_transaction_id: payment_data.connector_response.connector_transaction_id, + payload: json_payload, connector_meta: payment_data.payment_attempt.connector_metadata, }) } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index d224c375cd..f747d1281d 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -109,22 +109,21 @@ impl Payments { web::resource("/{payment_id}/capture").route(web::post().to(payments_capture)), ) .service( - web::resource("/start/{payment_id}/{merchant_id}/{attempt_id}") + web::resource("/redirect/{payment_id}/{merchant_id}/{attempt_id}") .route(web::get().to(payments_start)), ) .service( web::resource( - "/{payment_id}/{merchant_id}/response/{connector}/{creds_identifier}", + "/{payment_id}/{merchant_id}/redirect/response/{connector}/{creds_identifier}", ) .route(web::get().to(payments_redirect_response_with_creds_identifier)), ) .service( - web::resource("/{payment_id}/{merchant_id}/response/{connector}") + web::resource("/{payment_id}/{merchant_id}/redirect/response/{connector}") .route(web::get().to(payments_redirect_response)), ) .service( - web::resource("/{payment_id}/{merchant_id}/complete/{connector}") - .route(web::get().to(payments_complete_authorize)) + web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}") .route(web::post().to(payments_complete_authorize)), ); } diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index d8d449cc5d..4a0bae0801 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -60,12 +60,12 @@ pub async fn payments_create( .await } -// /// Payments - Start +// /// Payments - Redirect // /// -// /// The entry point for a payment which involves the redirection flow. This redirects the user to the authentication page +// /// For a payment which involves the redirection flow. This redirects the user to the authentication page // #[utoipa::path( // get, -// path = "/payments/start/{payment_id}/{merchant_id}/{attempt_id}", +// path = "/payments/redirect/{payment_id}/{merchant_id}/{attempt_id}", // params( // ("payment_id" = String, Path, description = "The identifier for payment"), // ("merchant_id" = String, Path, description = "The identifier for merchant"), diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 2ad76a662e..32d16427c6 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -27,7 +27,7 @@ use crate::{ logger, routes::{app::AppStateInfo, metrics, AppState}, services::authentication as auth, - types::{self, api, storage, ErrorResponse}, + types::{self, api, ErrorResponse}, }; pub type BoxedConnectorIntegration<'a, T, Req, Resp> = @@ -447,19 +447,6 @@ pub struct ApplicationRedirectResponse { pub url: String, } -impl From<&storage::PaymentAttempt> for ApplicationRedirectResponse { - fn from(payment_attempt: &storage::PaymentAttempt) -> Self { - Self { - url: format!( - "/payments/start/{}/{}/{}", - &payment_attempt.payment_id, - &payment_attempt.merchant_id, - &payment_attempt.attempt_id - ), - } - } -} - #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct RedirectForm { pub endpoint: String, diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 98b5de4125..b5200d51dc 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -199,6 +199,7 @@ pub struct CompleteAuthorizeData { pub mandate_id: Option, pub off_session: Option, pub setup_mandate_details: Option, + pub payload: Option, pub browser_info: Option, pub connector_transaction_id: Option, pub connector_meta: Option, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index fc38a11de0..54a8547e30 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -128,7 +128,10 @@ impl ForeignFrom for storage_enums::IntentStatus { storage_enums::AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod, storage_enums::AttemptStatus::Authorized => Self::RequiresCapture, - storage_enums::AttemptStatus::AuthenticationPending => Self::RequiresCustomerAction, + storage_enums::AttemptStatus::AuthenticationPending + | storage_enums::AttemptStatus::DeviceDataCollectionPending => { + Self::RequiresCustomerAction + } storage_enums::AttemptStatus::Unresolved => Self::RequiresMerchantAction, storage_enums::AttemptStatus::PartialCharged diff --git a/crates/router/tests/connectors/airwallex.rs b/crates/router/tests/connectors/airwallex.rs index eead636831..f2e026144c 100644 --- a/crates/router/tests/connectors/airwallex.rs +++ b/crates/router/tests/connectors/airwallex.rs @@ -63,6 +63,7 @@ fn payment_method_details() -> Option { }), capture_method: Some(storage_models::enums::CaptureMethod::Manual), router_return_url: Some("https://google.com".to_string()), + complete_authorize_url: Some("https://google.com".to_string()), ..utils::PaymentAuthorizeType::default().0 }) } diff --git a/crates/storage_models/src/enums.rs b/crates/storage_models/src/enums.rs index c54ef1a138..e937728a58 100644 --- a/crates/storage_models/src/enums.rs +++ b/crates/storage_models/src/enums.rs @@ -57,6 +57,7 @@ pub enum AttemptStatus { Failure, PaymentMethodAwaited, ConfirmationAwaited, + DeviceDataCollectionPending, } #[derive( diff --git a/migrations/2023-03-16-105114_add_data_collection_status/down.sql b/migrations/2023-03-16-105114_add_data_collection_status/down.sql new file mode 100644 index 0000000000..5c27247a0a --- /dev/null +++ b/migrations/2023-03-16-105114_add_data_collection_status/down.sql @@ -0,0 +1,5 @@ +DELETE FROM pg_enum +WHERE enumlabel = 'device_data_collection_pending' +AND enumtypid = ( + SELECT oid FROM pg_type WHERE typname = 'AttemptStatus' +) \ No newline at end of file diff --git a/migrations/2023-03-16-105114_add_data_collection_status/up.sql b/migrations/2023-03-16-105114_add_data_collection_status/up.sql new file mode 100644 index 0000000000..f9674a39b6 --- /dev/null +++ b/migrations/2023-03-16-105114_add_data_collection_status/up.sql @@ -0,0 +1 @@ +ALTER TYPE "AttemptStatus" ADD VALUE IF NOT EXISTS 'device_data_collection_pending'; \ No newline at end of file