mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(connector) : [Globalpay]add mandates and bank redirects support for globalpay (#830)
Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
This commit is contained in:
@ -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<Value>,
|
||||
_action: services::PaymentAction,
|
||||
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
|
||||
let query = serde_urlencoded::from_str::<response::GlobalpayRedirectResponse>(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,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,9 +332,6 @@ pub struct Card {
|
||||
pub chip_condition: Option<ChipCondition>,
|
||||
/// The numeric value printed on the physical card.
|
||||
pub cvv: Secret<String>,
|
||||
/// 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<String>,
|
||||
/// 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<Model>,
|
||||
/// The reason stored credentials are being used to to create a transaction.
|
||||
pub reason: Option<Reason>,
|
||||
/// Indicates the order of this transaction in the sequence of a planned repeating
|
||||
/// transaction processing model.
|
||||
pub sequence: Option<Sequence>,
|
||||
@ -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")]
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use super::requests;
|
||||
|
||||
@ -133,8 +132,6 @@ pub struct PaymentMethod {
|
||||
pub message: Option<String>,
|
||||
/// Result code from the payment method provider.
|
||||
pub result: Option<String>,
|
||||
/// Redirect url for payment method provider
|
||||
pub redirect_url: Option<Url>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
/// URL to redirect the payer from the merchant's system to the payment method's system.
|
||||
pub redirect_url: Option<Url>,
|
||||
//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<String>,
|
||||
/// 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<String>,
|
||||
@ -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<GlobalpayPaymentStatus>,
|
||||
}
|
||||
|
||||
@ -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<Option<enums::CaptureMethod>> for requests::CaptureMode {
|
||||
fn get_payment_response(
|
||||
status: enums::AttemptStatus,
|
||||
response: GlobalpayPaymentsResponse,
|
||||
redirection_data: Option<RedirectForm>,
|
||||
) -> Result<types::PaymentsResponseData, ErrorResponse> {
|
||||
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<F, T>
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
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<String>,
|
||||
) -> Result<PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
|
||||
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<String> {
|
||||
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<Initiator>, Option<StoredCredential>, Option<String>);
|
||||
fn get_mandate_details(
|
||||
item: &types::PaymentsAuthorizeRouterData,
|
||||
) -> Result<MandateDetails, error_stack::Report<errors::ConnectorError>> {
|
||||
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<PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
|
||||
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<PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
|
||||
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),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,6 +145,7 @@ pub trait PaymentsAuthorizeRequestData {
|
||||
fn get_browser_info(&self) -> Result<types::BrowserInformation, Error>;
|
||||
fn get_card(&self) -> Result<api::Card, Error>;
|
||||
fn get_return_url(&self) -> Result<String, Error>;
|
||||
fn is_mandate_payment(&self) -> bool;
|
||||
fn get_webhook_url(&self) -> Result<String, Error>;
|
||||
}
|
||||
|
||||
@ -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<String, Error> {
|
||||
self.router_return_url
|
||||
.clone()
|
||||
|
||||
@ -141,7 +141,6 @@ default_imp_for_connector_redirect_response!(
|
||||
connector::Cybersource,
|
||||
connector::Dlocal,
|
||||
connector::Fiserv,
|
||||
connector::Globalpay,
|
||||
connector::Klarna,
|
||||
connector::Multisafepay,
|
||||
connector::Opennode,
|
||||
|
||||
Reference in New Issue
Block a user