diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index bbd2040c6b..f0dbe28192 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -118,6 +118,12 @@ pub(crate) enum ErrorCode { )] PaymentFailed, + #[error( + error_type = StripeErrorType::InvalidRequestError, code = "", + message = "The verification did not succeeded" + )] + VerificationFailed { data: Option }, + #[error( error_type = StripeErrorType::InvalidRequestError, code = "", message = "Reached maximum refund attempts" @@ -322,6 +328,7 @@ impl From for ErrorCode { | ApiErrorResponse::PaymentAuthenticationFailed { data } => { ErrorCode::PaymentIntentAuthenticationFailure { data } } + ApiErrorResponse::VerificationFailed { data } => ErrorCode::VerificationFailed { data }, ApiErrorResponse::PaymentCaptureFailed { data } => { ErrorCode::PaymentIntentPaymentAttemptFailed { data } } @@ -408,6 +415,7 @@ impl actix_web::ResponseError for ErrorCode { | ErrorCode::DuplicateMerchantConnectorAccount | ErrorCode::DuplicatePaymentMethod | ErrorCode::PaymentFailed + | ErrorCode::VerificationFailed { .. } | ErrorCode::MaximumRefundCount | ErrorCode::PaymentIntentInvalidParameter { .. } | ErrorCode::SerdeQsError { .. } diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index 899024f35e..72f9a0df01 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -53,6 +53,17 @@ impl api::PaymentAuthorize for Aci {} impl api::PaymentSync for Aci {} impl api::PaymentVoid for Aci {} impl api::PaymentCapture for Aci {} +impl api::PreVerify for Aci {} + +impl + services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > for Aci +{ + // TODO: Critical Implement +} impl services::ConnectorIntegration< diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index a4369d69f1..c9ed24cd74 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -53,6 +53,17 @@ impl api::PaymentAuthorize for Adyen {} impl api::PaymentSync for Adyen {} impl api::PaymentVoid for Adyen {} impl api::PaymentCapture for Adyen {} +impl api::PreVerify for Adyen {} + +impl + services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > for Adyen +{ + // TODO: Critical implement +} impl services::ConnectorIntegration< diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs index 8276446bd3..38f3eaedae 100644 --- a/crates/router/src/connector/authorizedotnet.rs +++ b/crates/router/src/connector/authorizedotnet.rs @@ -43,6 +43,17 @@ impl api::PaymentAuthorize for Authorizedotnet {} impl api::PaymentSync for Authorizedotnet {} impl api::PaymentVoid for Authorizedotnet {} impl api::PaymentCapture for Authorizedotnet {} +impl api::PreVerify for Authorizedotnet {} + +impl + services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > for Authorizedotnet +{ + // TODO: Critical Implement +} impl services::ConnectorIntegration< diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index ec1e698116..55de2cb4ec 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -51,6 +51,16 @@ impl api::PaymentAuthorize for Braintree {} impl api::PaymentSync for Braintree {} impl api::PaymentVoid for Braintree {} impl api::PaymentCapture for Braintree {} +impl api::PreVerify for Braintree {} + +impl + services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > for Braintree +{ +} #[allow(dead_code)] impl @@ -264,7 +274,6 @@ impl } } -#[allow(dead_code)] impl services::ConnectorIntegration< api::Void, diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index 6165038f34..64d20f9659 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -57,6 +57,17 @@ impl api::PaymentAuthorize for Checkout {} impl api::PaymentSync for Checkout {} impl api::PaymentVoid for Checkout {} impl api::PaymentCapture for Checkout {} +impl api::PreVerify for Checkout {} + +impl + services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > for Checkout +{ + // TODO: Critical Implement +} impl services::ConnectorIntegration< diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 2e7dac4b82..50b28234db 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -55,6 +55,7 @@ impl api::PaymentAuthorize for Stripe {} impl api::PaymentSync for Stripe {} impl api::PaymentVoid for Stripe {} impl api::PaymentCapture for Stripe {} +impl api::PreVerify for Stripe {} impl services::ConnectorIntegration< @@ -470,6 +471,132 @@ impl } } +#[allow(dead_code)] +type Verify = dyn services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, +>; +impl + services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > for Stripe +{ + fn get_headers( + &self, + req: &types::RouterData, + ) -> CustomResult, errors::ConnectorError> { + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + Verify::get_content_type(self).to_string(), + ), + (headers::X_ROUTER.to_string(), "test".to_string()), + ]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RouterData< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + >, + connectors: Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "v1/setup_intents" + )) + } + + fn get_request_body( + &self, + req: &types::RouterData, + ) -> CustomResult, errors::ConnectorError> { + let stripe_req = utils::Encode::::convert_and_url_encode(req) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(stripe_req)) + } + + fn build_request( + &self, + req: &types::RouterData, + connectors: Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&Verify::get_url(self, req, connectors)?) + .headers(Verify::get_headers(self, req)?) + .header(headers::X_ROUTER, "test") + .body(Verify::get_request_body(self, req)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RouterData< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + >, + res: Response, + ) -> CustomResult< + types::RouterData, + errors::ConnectorError, + > + where + api::Verify: Clone, + types::VerifyRequestData: Clone, + types::PaymentsResponseData: Clone, + { + let response: stripe::SetupIntentResponse = res + .response + .parse_struct("SetupIntentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + logger::debug!(setup_intent_response=?response); + 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: Bytes, + ) -> CustomResult { + let response: stripe::ErrorResponse = res + .parse_struct("ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(ErrorResponse { + code: response + .error + .code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .error + .message + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: None, + }) + } + // TODO CRITICAL: Implement for POC +} + impl api::Refund for Stripe {} impl api::RefundExecute for Stripe {} impl api::RefundSync for Stripe {} diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 5c02f4a31b..6a3747e037 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -87,6 +87,22 @@ pub struct PaymentIntentRequest { pub capture_method: StripeCaptureMethod, } +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct SetupIntentRequest { + pub statement_descriptor_suffix: Option, + #[serde(rename = "metadata[order_id]")] + pub metadata_order_id: String, + #[serde(rename = "metadata[txn_id]")] + pub metadata_txn_id: String, + #[serde(rename = "metadata[txn_uuid]")] + pub metadata_txn_uuid: String, + pub confirm: bool, + pub setup_future_usage: Option, + pub off_session: Option, + #[serde(flatten)] + pub payment_data: StripePaymentMethodData, +} + #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripeCardData { #[serde(rename = "payment_method_types[]")] @@ -214,6 +230,29 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { } } +impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest { + type Error = error_stack::Report; + fn try_from(item: &types::VerifyRouterData) -> Result { + let metadata_order_id = item.payment_id.to_string(); + let metadata_txn_id = format!("{}_{}_{}", item.merchant_id, item.payment_id, "1"); + let metadata_txn_uuid = Uuid::new_v4().to_string(); + + let payment_data: StripePaymentMethodData = + (item.request.payment_method_data.clone(), item.auth_type).into(); + + Ok(Self { + confirm: true, + metadata_order_id, + metadata_txn_id, + metadata_txn_uuid, + payment_data, + statement_descriptor_suffix: item.request.statement_descriptor_suffix.clone(), + off_session: item.request.off_session, + setup_future_usage: item.request.setup_future_usage, + }) + } +} + // PaymentIntentResponse #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] @@ -281,6 +320,19 @@ pub struct PaymentIntentResponse { pub next_action: Option, } +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] +pub struct SetupIntentResponse { + pub id: String, + pub object: String, + pub status: StripePaymentStatus, // Change to SetupStatus + pub client_secret: Secret, + pub customer: Option, + pub statement_descriptor: Option, + pub statement_descriptor_suffix: Option, + pub metadata: StripeMetadata, + pub next_action: Option, +} + impl TryFrom> for types::RouterData @@ -323,6 +375,43 @@ impl } } +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + let redirection_data = item.response.next_action.as_ref().map( + |StripeNextActionResponse::RedirectToUrl(response)| { + let mut base_url = response.url.clone(); + base_url.set_query(None); + services::RedirectForm { + url: base_url.to_string(), + method: services::Method::Get, + form_fields: std::collections::HashMap::from_iter( + response + .url + .query_pairs() + .map(|(k, v)| (k.to_string(), v.to_string())), + ), + } + }, + ); + + Ok(types::RouterData { + status: enums::AttemptStatus::from(item.response.status), + response: Ok(types::PaymentsResponseData { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + redirect: redirection_data.is_some(), + redirection_data, + }), + ..item.data + }) + } +} + #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[serde(rename_all = "snake_case", remote = "Self")] pub enum StripeNextActionResponse { @@ -622,3 +711,36 @@ pub struct StripeWebhookObjectEventType { pub struct StripeWebhookObjectId { pub data: StripeWebhookDataId, } + +impl From<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentMethodData { + fn from((pm_data, auth_type): (api::PaymentMethod, enums::AuthenticationType)) -> Self { + match pm_data { + api::PaymentMethod::Card(ref ccard) => StripePaymentMethodData::Card({ + let payment_method_auth_type = match auth_type { + enums::AuthenticationType::ThreeDs => Auth3ds::Any, + enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic, + }; + StripeCardData { + payment_method_types: "card".to_string(), + payment_method_data_type: "card".to_string(), + payment_method_data_card_number: ccard.card_number.clone(), + payment_method_data_card_exp_month: ccard.card_exp_month.clone(), + payment_method_data_card_exp_year: ccard.card_exp_year.clone(), + payment_method_data_card_cvc: ccard.card_cvc.clone(), + payment_method_auth_type, + } + }), + api::PaymentMethod::BankTransfer => StripePaymentMethodData::Bank, + api::PaymentMethod::PayLater(ref klarna_data) => { + StripePaymentMethodData::Klarna(StripeKlarnaData { + payment_method_types: "klarna".to_string(), + payment_method_data_type: "klarna".to_string(), + billing_email: klarna_data.billing_email.clone(), + billing_country: klarna_data.country.clone(), + }) + } + api::PaymentMethod::Wallet(_) => StripePaymentMethodData::Wallet, + api::PaymentMethod::Paypal => StripePaymentMethodData::Paypal, + } + } +} diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs index 072923a3c1..9a2377ae42 100644 --- a/crates/router/src/core/errors/api_error_response.rs +++ b/crates/router/src/core/errors/api_error_response.rs @@ -79,6 +79,8 @@ pub enum ApiErrorResponse { CardExpired { data: Option }, #[error(error_type = ErrorType::ProcessingError, code = "CE_06", message = "Refund failed while processing with connector. Retry refund.")] RefundFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Verification failed while processing with connector. Retry operation.")] + VerificationFailed { data: Option }, #[error(error_type = ErrorType::ServerNotAvailable, code = "RE_00", message = "Something went wrong.")] InternalServerError, @@ -154,6 +156,7 @@ impl actix_web::ResponseError for ApiErrorResponse { | ApiErrorResponse::InvalidCardData { .. } | ApiErrorResponse::CardExpired { .. } | ApiErrorResponse::RefundFailed { .. } + | ApiErrorResponse::VerificationFailed { .. } | ApiErrorResponse::PaymentUnexpectedState { .. } => StatusCode::BAD_REQUEST, // 400 ApiErrorResponse::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, // 500 diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index 15c3671a15..749ddad5a0 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -1,74 +1,76 @@ use super::DatabaseError; -use crate::logger; +use crate::{core::errors, logger}; pub(crate) trait StorageErrorExt { fn to_not_found_response( self, - not_found_response: super::ApiErrorResponse, - ) -> error_stack::Report; + not_found_response: errors::ApiErrorResponse, + ) -> error_stack::Report; fn to_duplicate_response( self, - duplicate_response: super::ApiErrorResponse, - ) -> error_stack::Report; + duplicate_response: errors::ApiErrorResponse, + ) -> error_stack::Report; } -impl StorageErrorExt for error_stack::Report { +impl StorageErrorExt for error_stack::Report { fn to_not_found_response( self, - not_found_response: super::ApiErrorResponse, - ) -> error_stack::Report { + not_found_response: errors::ApiErrorResponse, + ) -> error_stack::Report { match self.current_context() { - super::StorageError::DatabaseError(DatabaseError::NotFound) => { + errors::StorageError::DatabaseError(DatabaseError::NotFound) => { self.change_context(not_found_response) } - _ => self.change_context(super::ApiErrorResponse::InternalServerError), + _ => self.change_context(errors::ApiErrorResponse::InternalServerError), } } fn to_duplicate_response( self, - duplicate_response: super::ApiErrorResponse, - ) -> error_stack::Report { + duplicate_response: errors::ApiErrorResponse, + ) -> error_stack::Report { match self.current_context() { - super::StorageError::DatabaseError(DatabaseError::UniqueViolation) => { + errors::StorageError::DatabaseError(DatabaseError::UniqueViolation) => { self.change_context(duplicate_response) } - _ => self.change_context(super::ApiErrorResponse::InternalServerError), + _ => self.change_context(errors::ApiErrorResponse::InternalServerError), } } } pub(crate) trait ApiClientErrorExt { - fn to_unsuccessful_processing_step_response(self) - -> error_stack::Report; -} - -impl ApiClientErrorExt for error_stack::Report { fn to_unsuccessful_processing_step_response( self, - ) -> error_stack::Report { + ) -> error_stack::Report; +} + +impl ApiClientErrorExt for error_stack::Report { + fn to_unsuccessful_processing_step_response( + self, + ) -> error_stack::Report { let data = match self.current_context() { - super::ApiClientError::BadRequestReceived(bytes) - | super::ApiClientError::UnauthorizedReceived(bytes) - | super::ApiClientError::NotFoundReceived(bytes) - | super::ApiClientError::UnprocessableEntityReceived(bytes) => Some(bytes.clone()), + errors::ApiClientError::BadRequestReceived(bytes) + | errors::ApiClientError::UnauthorizedReceived(bytes) + | errors::ApiClientError::NotFoundReceived(bytes) + | errors::ApiClientError::UnprocessableEntityReceived(bytes) => Some(bytes.clone()), _ => None, }; - self.change_context(super::ConnectorError::ProcessingStepFailed(data)) + self.change_context(errors::ConnectorError::ProcessingStepFailed(data)) } } pub(crate) trait ConnectorErrorExt { - fn to_refund_failed_response(self) -> error_stack::Report; - fn to_payment_failed_response(self) -> error_stack::Report; + fn to_refund_failed_response(self) -> error_stack::Report; + fn to_payment_failed_response(self) -> error_stack::Report; + fn to_verify_failed_response(self) -> error_stack::Report; } // FIXME: The implementation can be improved by handling BOM maybe? -impl ConnectorErrorExt for error_stack::Report { - fn to_refund_failed_response(self) -> error_stack::Report { +impl ConnectorErrorExt for error_stack::Report { + fn to_refund_failed_response(self) -> error_stack::Report { let data = match self.current_context() { - super::ConnectorError::ProcessingStepFailed(Some(bytes)) => { + errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => { let response_str = std::str::from_utf8(bytes); match response_str { Ok(s) => serde_json::from_str(s) @@ -84,12 +86,12 @@ impl ConnectorErrorExt for error_stack::Report { } _ => None, }; - self.change_context(super::ApiErrorResponse::RefundFailed { data }) + self.change_context(errors::ApiErrorResponse::RefundFailed { data }) } - fn to_payment_failed_response(self) -> error_stack::Report { + fn to_payment_failed_response(self) -> error_stack::Report { let data = match self.current_context() { - super::ConnectorError::ProcessingStepFailed(Some(bytes)) => { + errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => { let response_str = std::str::from_utf8(bytes); match response_str { Ok(s) => serde_json::from_str(s) @@ -105,6 +107,25 @@ impl ConnectorErrorExt for error_stack::Report { } _ => None, }; - self.change_context(super::ApiErrorResponse::PaymentAuthorizationFailed { data }) + self.change_context(errors::ApiErrorResponse::PaymentAuthorizationFailed { data }) + } + + fn to_verify_failed_response(self) -> error_stack::Report { + let data = match self.current_context() { + errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => { + let response_str = std::str::from_utf8(bytes); + match response_str { + Ok(s) => serde_json::from_str(s) + .map_err(|err| logger::error!(%err, "Failed to convert response to JSON")) + .ok(), + Err(err) => { + logger::error!(%err, "Failed to convert response to UTF8 string"); + None + } + } + } + _ => None, + }; + self.change_context(errors::ApiErrorResponse::PaymentAuthorizationFailed { data }) } } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 1cb640bfbf..063eb131d1 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -2,6 +2,7 @@ mod authorize_flow; mod cancel_flow; mod capture_flow; mod psync_flow; +mod verfiy_flow; use async_trait::async_trait; diff --git a/crates/router/src/core/payments/flows/verfiy_flow.rs b/crates/router/src/core/payments/flows/verfiy_flow.rs new file mode 100644 index 0000000000..72abc0f8e4 --- /dev/null +++ b/crates/router/src/core/payments/flows/verfiy_flow.rs @@ -0,0 +1,175 @@ +use async_trait::async_trait; +use error_stack::ResultExt; +use masking::Secret; + +use super::{ConstructFlowSpecificData, Feature}; +use crate::{ + consts, + core::{ + errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt}, + payments::{self, helpers, transformers, PaymentData}, + }, + routes::AppState, + services, + types::{ + self, api, + storage::{self, enums}, + }, + utils, +}; + +#[async_trait] +impl ConstructFlowSpecificData + for PaymentData +{ + async fn construct_r_d<'a>( + &self, + state: &AppState, + connector_id: &str, + merchant_account: &storage::MerchantAccount, + ) -> RouterResult { + let (_, router_data) = transformers::construct_payment_router_data::< + api::Verify, + types::VerifyRequestData, + >(state, self.clone(), connector_id, merchant_account) + .await?; + + Ok(router_data) + } +} + +#[async_trait] +impl Feature for types::VerifyRouterData { + async fn decide_flows<'a>( + self, + state: &AppState, + connector: api::ConnectorData, + customer: &Option, + payment_data: PaymentData, + call_connector_action: payments::CallConnectorAction, + ) -> (RouterResult, PaymentData) + where + dyn api::Connector: services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + >, + { + let resp = self + .decide_flow( + state, + connector, + customer, + Some(true), + call_connector_action, + ) + .await; + + (resp, payment_data) + } +} + +impl types::VerifyRouterData { + pub async fn decide_flow<'a, 'b>( + &'b self, + state: &'a AppState, + connector: api::ConnectorData, + maybe_customer: &Option, + confirm: Option, + call_connector_action: payments::CallConnectorAction, + ) -> RouterResult + where + dyn api::Connector + Sync: services::ConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + >, + { + match confirm { + Some(true) => { + let connector_integration: services::BoxedConnectorIntegration< + api::Verify, + types::VerifyRequestData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + let mut resp = services::execute_connector_processing_step( + state, + connector_integration, + self, + call_connector_action, + ) + .await + .map_err(|err| err.to_verify_failed_response())?; + + match &self.request.mandate_id { + Some(mandate_id) => { + let mandate = state + .store + .find_mandate_by_merchant_id_mandate_id(&resp.merchant_id, mandate_id) + .await + .change_context(errors::ApiErrorResponse::MandateNotFound)?; + resp.payment_method_id = Some(mandate.payment_method_id); + } + None => { + if self.request.setup_future_usage.is_some() { + let payment_method_id = helpers::call_payment_method( + state, + &self.merchant_id, + Some(&self.request.payment_method_data), + Some(self.payment_method), + maybe_customer, + ) + .await? + .payment_method_id; + + resp.payment_method_id = Some(payment_method_id.clone()); + if let Some(new_mandate_data) = generate_mandate( + self.merchant_id.clone(), + self.request.setup_mandate_details.clone(), + maybe_customer, + payment_method_id, + ) { + resp.request.mandate_id = Some(new_mandate_data.mandate_id.clone()); + state.store.insert_mandate(new_mandate_data).await.map_err( + |err| { + err.to_duplicate_response( + errors::ApiErrorResponse::DuplicateMandate, + ) + }, + )?; + } + } + } + } + Ok(resp) + } + _ => Ok(self.clone()), + } + } +} + +fn generate_mandate( + merchant_id: String, + setup_mandate_details: Option, + customer: &Option, + payment_method_id: String, +) -> Option { + match (setup_mandate_details, customer) { + (Some(data), Some(cus)) => { + let mandate_id = utils::generate_id(consts::ID_LENGTH, "man"); + Some(storage::MandateNew { + mandate_id, + customer_id: cus.customer_id.clone(), + merchant_id, + payment_method_id, + mandate_status: enums::MandateStatus::Active, + mandate_type: enums::MandateType::MultiUse, + customer_ip_address: data.customer_acceptance.get_ip_address().map(Secret::new), + customer_user_agent: data.customer_acceptance.get_user_agent(), + customer_accepted_at: Some(data.customer_acceptance.get_accepted_at()), + ..Default::default() + }) + } + (_, _) => None, + } +} diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 7e98dd588b..adba45ff16 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -328,3 +328,31 @@ impl TryFrom> for types::PaymentsCancelData { }) } } + +impl TryFrom> for types::VerifyRequestData { + type Error = error_stack::Report; + + fn try_from(payment_data: PaymentData) -> Result { + Ok(Self { + confirm: true, + payment_method_data: { + let payment_method_type = payment_data + .payment_attempt + .payment_method + .get_required_value("payment_method_type")?; + + match payment_method_type { + enums::PaymentMethodType::Paypal => api::PaymentMethod::Paypal, + _ => payment_data + .payment_method_data + .get_required_value("payment_method_data")?, + } + }, + statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, + setup_future_usage: payment_data.payment_intent.setup_future_usage, + mandate_id: payment_data.mandate_id.clone(), + off_session: payment_data.mandate_id.as_ref().map(|_| true), + setup_mandate_details: payment_data.setup_mandate, + }) + } +} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 903116810d..da5a6dcfbe 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -49,6 +49,8 @@ pub type RefundExecuteType = pub type RefundSyncType = dyn services::ConnectorIntegration; +pub type VerifyRouterData = RouterData; + #[derive(Debug, Clone)] pub struct RouterData { pub flow: PhantomData, diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 231cb7fd7d..d779c5a5d9 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -78,10 +78,10 @@ pub struct PaymentsRedirectRequest { pub param: String, } -#[derive(Default, Debug, serde::Deserialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct VerifyRequest { - pub merchant_id: Option, + pub merchant_id: String, pub customer_id: Option, pub email: Option>, pub name: Option>, @@ -246,6 +246,9 @@ pub struct PSync; #[derive(Debug, Clone)] pub struct Void; +#[derive(Debug, Clone)] +pub struct Verify; + //#[derive(Debug, serde::Deserialize, serde::Serialize)] //#[serde(untagged)] //pub enum enums::CaptureMethod { @@ -650,8 +653,13 @@ pub trait PaymentCapture: { } +pub trait PreVerify: + api::ConnectorIntegration +{ +} + pub trait Payment: - ConnectorCommon + PaymentAuthorize + PaymentSync + PaymentCapture + PaymentVoid + ConnectorCommon + PaymentAuthorize + PaymentSync + PaymentCapture + PaymentVoid + PreVerify { } #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)]