From ce912dd8520fe4ffb0cfd83644e126d854858f03 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 11 Apr 2023 17:46:01 +0530 Subject: [PATCH] feat(connector) : [Globalpay]add mandates and bank redirects support for globalpay (#830) Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com> --- crates/router/src/connector/globalpay.rs | 29 ++- .../src/connector/globalpay/requests.rs | 20 +- .../src/connector/globalpay/response.rs | 16 +- .../src/connector/globalpay/transformers.rs | 209 +++++++++++++----- crates/router/src/connector/utils.rs | 9 + crates/router/src/core/payments/flows.rs | 1 - 6 files changed, 206 insertions(+), 78 deletions(-) diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 9c74025db2..7f9e7b10c5 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -18,7 +18,10 @@ use self::{ use crate::{ configs::settings, connector::utils as conn_utils, - core::errors::{self, CustomResult}, + core::{ + errors::{self, CustomResult}, + payments, + }, db, headers, services::{self, ConnectorIntegration}, types::{ @@ -882,3 +885,27 @@ impl api::IncomingWebhook for Globalpay { Ok(res_json) } } + +impl services::ConnectorRedirectResponse for Globalpay { + fn get_flow_type( + &self, + query_params: &str, + _json_payload: Option, + _action: services::PaymentAction, + ) -> CustomResult { + let query = serde_urlencoded::from_str::(query_params) + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(query.status.map_or( + payments::CallConnectorAction::Trigger, + |status| match status { + response::GlobalpayPaymentStatus::Captured => { + payments::CallConnectorAction::StatusUpdate( + storage_models::enums::AttemptStatus::from(status), + ) + } + _ => payments::CallConnectorAction::Trigger, + }, + )) + } +} diff --git a/crates/router/src/connector/globalpay/requests.rs b/crates/router/src/connector/globalpay/requests.rs index 9e823049a9..b6b34af0aa 100644 --- a/crates/router/src/connector/globalpay/requests.rs +++ b/crates/router/src/connector/globalpay/requests.rs @@ -332,9 +332,6 @@ pub struct Card { pub chip_condition: Option, /// The numeric value printed on the physical card. pub cvv: Secret, - /// Card Verification Value Indicator sent by the Merchant indicating the CVV - /// availability. - pub cvv_indicator: CvvIndicator, /// The 2 digit expiry date month of the card. pub expiry_month: Secret, /// The 2 digit expiry date year of the card. @@ -375,8 +372,6 @@ pub struct StoredCredential { /// Indicates the transaction processing model being executed when using stored /// credentials. pub model: Option, - /// The reason stored credentials are being used to to create a transaction. - pub reason: Option, /// Indicates the order of this transaction in the sequence of a planned repeating /// transaction processing model. pub sequence: Option, @@ -578,6 +573,7 @@ pub enum ApmProvider { Ideal, Paypal, Sofort, + Eps, Testpay, } @@ -642,20 +638,6 @@ pub enum ChipCondition { PrevSuccess, } -/// Card Verification Value Indicator sent by the Merchant indicating the CVV -/// availability. -#[derive(Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum CvvIndicator { - /// indicates the cvv is present but cannot be read. - Illegible, - /// indicates the cvv is not present on the card. - NotPresent, - #[default] - /// indicates the cvv is present. - Present, -} - /// Indicates whether the card is a debit or credit card. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] diff --git a/crates/router/src/connector/globalpay/response.rs b/crates/router/src/connector/globalpay/response.rs index 5aac13e06a..0542af1230 100644 --- a/crates/router/src/connector/globalpay/response.rs +++ b/crates/router/src/connector/globalpay/response.rs @@ -1,5 +1,4 @@ use serde::{Deserialize, Serialize}; -use url::Url; use super::requests; @@ -133,8 +132,6 @@ pub struct PaymentMethod { pub message: Option, /// Result code from the payment method provider. pub result: Option, - /// Redirect url for payment method provider - pub redirect_url: Option, } /// Data associated with the response of an APM transaction. @@ -156,7 +153,12 @@ pub struct Apm { /// The reference the payment method provider created for the transaction. pub provider_transaction_reference: Option, /// URL to redirect the payer from the merchant's system to the payment method's system. - pub redirect_url: Option, + //1)paypal sends redirect_url as provider_redirect_url for require_customer_action + //2)bankredirects sends redirect_url as redirect_url for require_customer_action + //3)after completeauthorize in paypal it doesn't send redirect_url + //4)after customer action in bankredirects it sends empty string in redirect_url + #[serde(alias = "provider_redirect_url")] + pub redirect_url: Option, /// A string generated by the payment method to represent the session created on the payment /// method's platform to facilitate the creation of a transaction. pub session_token: Option, @@ -283,6 +285,7 @@ pub enum ApmProvider { Ideal, Paypal, Sofort, + Eps, Testpay, } @@ -416,3 +419,8 @@ pub enum GlobalpayWebhookStatus { Declined, Captured, } + +#[derive(Debug, Serialize, Deserialize)] +pub struct GlobalpayRedirectResponse { + pub status: Option, +} diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index d6842397d4..a22d17cb37 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -1,17 +1,21 @@ use common_utils::crypto::{self, GenerateDigest}; -use error_stack::ResultExt; +use error_stack::{IntoReport, ResultExt}; use rand::distributions::DistString; use serde::{Deserialize, Serialize}; +use url::Url; use super::{ - requests::{self, GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest}, + requests::{ + self, ApmProvider, GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest, Initiator, + PaymentMethodData, StoredCredential, + }, response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse, GlobalpayRefreshTokenResponse}, }; use crate::{ - connector::utils::{self, RouterData, WalletData}, + connector::utils::{self, PaymentsAuthorizeRequestData, RouterData, WalletData}, consts, core::errors, - services::{self}, + services::{self, RedirectForm}, types::{self, api, storage::enums, ErrorResponse}, }; @@ -26,46 +30,8 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest { let metadata: GlobalPayMeta = utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?; let account_name = metadata.account_name; - let payment_method_data = match item.request.payment_method_data.clone() { - api::PaymentMethodData::Card(ccard) => { - requests::PaymentMethodData::Card(requests::Card { - number: ccard.card_number, - expiry_month: ccard.card_exp_month, - expiry_year: ccard.card_exp_year, - cvv: ccard.card_cvc, - account_type: None, - authcode: None, - avs_address: None, - avs_postal_code: None, - brand_reference: None, - chip_condition: None, - cvv_indicator: Default::default(), - funding: None, - pin_block: None, - tag: None, - track: None, - }) - } - api::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - api_models::payments::WalletData::PaypalRedirect(_) => { - requests::PaymentMethodData::Apm(requests::Apm { - provider: Some(requests::ApmProvider::Paypal), - }) - } - api_models::payments::WalletData::GooglePay(_) => { - requests::PaymentMethodData::DigitalWallet(requests::DigitalWallet { - provider: Some(requests::DigitalWalletProvider::PayByGoogle), - payment_token: wallet_data.get_wallet_token_as_json()?, - }) - } - _ => Err(errors::ConnectorError::NotImplemented( - "Payment methods".to_string(), - ))?, - }, - _ => Err(errors::ConnectorError::NotImplemented( - "Payment methods".to_string(), - ))?, - }; + let (initiator, stored_credential, brand_reference) = get_mandate_details(item)?; + let payment_method_data = get_payment_method_data(item, brand_reference)?; Ok(Self { account_name, amount: Some(item.request.amount.to_string()), @@ -87,7 +53,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest { storage_mode: None, }, notifications: Some(requests::Notifications { - return_url: item.request.complete_authorize_url.clone(), + return_url: get_return_url(item), challenge_return_url: None, decoupled_challenge_return_url: None, status_url: item.request.webhook_url.clone(), @@ -101,14 +67,14 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest { description: None, device: None, gratuity_amount: None, - initiator: None, + initiator, ip_address: None, language: None, lodging: None, order: None, payer_reference: None, site_reference: None, - stored_credential: None, + stored_credential, surcharge_amount: None, total_capture_count: None, globalpay_payments_request_type: None, @@ -226,11 +192,12 @@ impl From> for requests::CaptureMode { fn get_payment_response( status: enums::AttemptStatus, response: GlobalpayPaymentsResponse, + redirection_data: Option, ) -> Result { - let redirection_data = response.payment_method.as_ref().and_then(|payment_method| { - payment_method.redirect_url.as_ref().map(|redirect_url| { - services::RedirectForm::from((redirect_url.to_owned(), services::Method::Get)) - }) + let mandate_reference = response.payment_method.as_ref().and_then(|pm| { + pm.card + .as_ref() + .and_then(|card| card.brand_reference.to_owned()) }); match status { enums::AttemptStatus::Failure => Err(ErrorResponse { @@ -243,7 +210,7 @@ fn get_payment_response( _ => Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.id), redirection_data, - mandate_reference: None, + mandate_reference, connector_metadata: None, }), } @@ -263,9 +230,28 @@ impl >, ) -> Result { let status = enums::AttemptStatus::from(item.response.status); + let redirect_url = item + .response + .payment_method + .as_ref() + .and_then(|payment_method| { + payment_method + .apm + .as_ref() + .and_then(|apm| apm.redirect_url.as_ref()) + }) + .filter(|redirect_str| !redirect_str.is_empty()) + .map(|url| { + Url::parse(url) + .into_report() + .change_context(errors::ConnectorError::FailedToObtainIntegrationUrl) + }) + .transpose()?; + let redirection_data = + redirect_url.map(|url| services::RedirectForm::from((url, services::Method::Get))); Ok(Self { status, - response: get_payment_response(status, item.response), + response: get_payment_response(status, item.response, redirection_data), ..item.data }) } @@ -338,3 +324,120 @@ pub struct GlobalpayErrorResponse { pub detailed_error_code: String, pub detailed_error_description: String, } + +fn get_payment_method_data( + item: &types::PaymentsAuthorizeRouterData, + brand_reference: Option, +) -> Result> { + match &item.request.payment_method_data { + api::PaymentMethodData::Card(ccard) => Ok(PaymentMethodData::Card(requests::Card { + number: ccard.card_number.clone(), + expiry_month: ccard.card_exp_month.clone(), + expiry_year: ccard.card_exp_year.clone(), + cvv: ccard.card_cvc.clone(), + account_type: None, + authcode: None, + avs_address: None, + avs_postal_code: None, + brand_reference, + chip_condition: None, + funding: None, + pin_block: None, + tag: None, + track: None, + })), + api::PaymentMethodData::Wallet(wallet_data) => get_wallet_data(wallet_data), + api::PaymentMethodData::BankRedirect(bank_redirect) => { + get_bank_redirect_data(bank_redirect) + } + _ => Err(errors::ConnectorError::NotImplemented( + "Payment methods".to_string(), + ))?, + } +} + +fn get_return_url(item: &types::PaymentsAuthorizeRouterData) -> Option { + match item.request.payment_method_data.clone() { + api::PaymentMethodData::Wallet(api_models::payments::WalletData::PaypalRedirect(_)) => { + item.request.complete_authorize_url.clone() + } + _ => item.request.router_return_url.clone(), + } +} + +type MandateDetails = (Option, Option, Option); +fn get_mandate_details( + item: &types::PaymentsAuthorizeRouterData, +) -> Result> { + Ok(if item.request.is_mandate_payment() { + let connector_mandate_id = item + .request + .mandate_id + .as_ref() + .and_then(|mandate_ids| mandate_ids.connector_mandate_id.clone()); + ( + Some(match item.request.off_session { + Some(true) => Initiator::Merchant, + _ => Initiator::Payer, + }), + Some(StoredCredential { + model: Some(requests::Model::Recurring), + sequence: Some(match connector_mandate_id.is_some() { + true => requests::Sequence::Subsequent, + false => requests::Sequence::First, + }), + }), + connector_mandate_id, + ) + } else { + (None, None, None) + }) +} + +fn get_wallet_data( + wallet_data: &api_models::payments::WalletData, +) -> Result> { + match wallet_data { + api_models::payments::WalletData::PaypalRedirect(_) => { + Ok(PaymentMethodData::Apm(requests::Apm { + provider: Some(ApmProvider::Paypal), + })) + } + api_models::payments::WalletData::GooglePay(_) => { + Ok(PaymentMethodData::DigitalWallet(requests::DigitalWallet { + provider: Some(requests::DigitalWalletProvider::PayByGoogle), + payment_token: wallet_data.get_wallet_token_as_json()?, + })) + } + _ => Err(errors::ConnectorError::NotImplemented( + "Payment methods".to_string(), + ))?, + } +} + +fn get_bank_redirect_data( + bank_redirect: &api_models::payments::BankRedirectData, +) -> Result> { + match bank_redirect { + api_models::payments::BankRedirectData::Eps { .. } => { + Ok(PaymentMethodData::Apm(requests::Apm { + provider: Some(ApmProvider::Eps), + })) + } + api_models::payments::BankRedirectData::Giropay { .. } => { + Ok(PaymentMethodData::Apm(requests::Apm { + provider: Some(ApmProvider::Giropay), + })) + } + api_models::payments::BankRedirectData::Ideal { .. } => { + Ok(PaymentMethodData::Apm(requests::Apm { + provider: Some(ApmProvider::Ideal), + })) + } + api_models::payments::BankRedirectData::Sofort { .. } => { + Ok(PaymentMethodData::Apm(requests::Apm { + provider: Some(ApmProvider::Sofort), + })) + } + } +} diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 209e4ac2c8..8ac36fe140 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -145,6 +145,7 @@ pub trait PaymentsAuthorizeRequestData { fn get_browser_info(&self) -> Result; fn get_card(&self) -> Result; fn get_return_url(&self) -> Result; + fn is_mandate_payment(&self) -> bool; fn get_webhook_url(&self) -> Result; } @@ -171,6 +172,14 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { .clone() .ok_or_else(missing_field_err("return_url")) } + fn is_mandate_payment(&self) -> bool { + self.setup_mandate_details.is_some() + || self + .mandate_id + .as_ref() + .and_then(|mandate_ids| mandate_ids.connector_mandate_id.as_ref()) + .is_some() + } fn get_webhook_url(&self) -> Result { self.router_return_url .clone() diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 6599a34df9..f9682eb5ea 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -141,7 +141,6 @@ default_imp_for_connector_redirect_response!( connector::Cybersource, connector::Dlocal, connector::Fiserv, - connector::Globalpay, connector::Klarna, connector::Multisafepay, connector::Opennode,