Skip to content

Commit

Permalink
feat(connector): implement auth and post auth flows for gpayments (#4746
Browse files Browse the repository at this point in the history
)

Co-authored-by: hrithikesh026 <[email protected]>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Hrithikesh <[email protected]>
  • Loading branch information
4 people authored Jun 11, 2024
1 parent 9f2476b commit d93f65f
Show file tree
Hide file tree
Showing 3 changed files with 459 additions and 23 deletions.
184 changes: 183 additions & 1 deletion crates/router/src/connector/gpayments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use transformers as gpayments;

use crate::{
configs::settings,
connector::{gpayments::gpayments_types::GpaymentsConnectorMetaData, utils::to_connector_meta},
core::errors::{self, CustomResult},
events::connector_api_logs::ConnectorEvent,
headers, services,
Expand Down Expand Up @@ -214,15 +215,196 @@ impl
types::authentication::AuthenticationResponseData,
> for Gpayments
{
}
fn get_headers(
&self,
req: &types::authentication::ConnectorAuthenticationRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, 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::authentication::ConnectorAuthenticationRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let connector_metadata: GpaymentsConnectorMetaData = to_connector_meta(
req.request
.pre_authentication_data
.connector_metadata
.clone(),
)?;
Ok(connector_metadata.authentication_url)
}

fn get_request_body(
&self,
req: &types::authentication::ConnectorAuthenticationRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = gpayments::GpaymentsRouterData::try_from((0, req))?;
let req_obj =
gpayments_types::GpaymentsAuthenticationRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(req_obj)))
}
fn build_request(
&self,
req: &types::authentication::ConnectorAuthenticationRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let gpayments_auth_type = gpayments::GpaymentsAuthType::try_from(&req.connector_auth_type)?;
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(
&types::authentication::ConnectorAuthenticationType::get_url(
self, req, connectors,
)?,
)
.attach_default_headers()
.headers(
types::authentication::ConnectorAuthenticationType::get_headers(
self, req, connectors,
)?,
)
.set_body(
types::authentication::ConnectorAuthenticationType::get_request_body(
self, req, connectors,
)?,
)
.add_certificate(Some(gpayments_auth_type.certificate))
.add_certificate_key(Some(gpayments_auth_type.private_key))
.build(),
))
}

fn handle_response(
&self,
data: &types::authentication::ConnectorAuthenticationRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<
types::authentication::ConnectorAuthenticationRouterData,
errors::ConnectorError,
> {
let response: gpayments_types::GpaymentsAuthenticationSuccessResponse = res
.response
.parse_struct("gpayments GpaymentsAuthenticationResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
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,
})
}

fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}
impl
ConnectorIntegration<
api::PostAuthentication,
types::authentication::ConnectorPostAuthenticationRequestData,
types::authentication::AuthenticationResponseData,
> for Gpayments
{
fn get_headers(
&self,
req: &types::authentication::ConnectorPostAuthenticationRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, 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::authentication::ConnectorPostAuthenticationRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let base_url = build_endpoint(self.base_url(connectors), &req.connector_meta_data)?;
Ok(format!(
"{}/api/v2/auth/brw/result?threeDSServerTransID={}",
base_url, req.request.threeds_server_transaction_id,
))
}

fn build_request(
&self,
req: &types::authentication::ConnectorPostAuthenticationRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let gpayments_auth_type = gpayments::GpaymentsAuthType::try_from(&req.connector_auth_type)?;
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(
&types::authentication::ConnectorPostAuthenticationType::get_url(
self, req, connectors,
)?,
)
.attach_default_headers()
.headers(
types::authentication::ConnectorPostAuthenticationType::get_headers(
self, req, connectors,
)?,
)
.add_certificate(Some(gpayments_auth_type.certificate))
.add_certificate_key(Some(gpayments_auth_type.private_key))
.build(),
))
}

fn handle_response(
&self,
data: &types::authentication::ConnectorPostAuthenticationRouterData,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<
types::authentication::ConnectorPostAuthenticationRouterData,
errors::ConnectorError,
> {
let response: gpayments_types::GpaymentsPostAuthenticationResponse = res
.response
.parse_struct("gpayments PaymentsSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
Ok(
types::authentication::ConnectorPostAuthenticationRouterData {
response: Ok(
types::authentication::AuthenticationResponseData::PostAuthNResponse {
trans_status: response.trans_status.into(),
authentication_value: response.authentication_value,
eci: response.eci,
},
),
..data.clone()
},
)
}

fn get_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}

impl
Expand Down
116 changes: 114 additions & 2 deletions crates/router/src/connector/gpayments/gpayments_types.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use api_models::payments::ThreeDsCompletionIndicator;
use cards::CardNumber;
use common_utils::types;
use masking::{Deserialize, Serialize};
use masking::{Deserialize, Secret, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct GpaymentsConnectorMetaData {
pub authentication_url: String,
pub three_ds_requestor_trans_id: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GpaymentsPreAuthVersionCallRequest {
Expand Down Expand Up @@ -38,6 +38,9 @@ pub struct TDS2ApiError {
pub error_description: String,
pub error_detail: Option<String>,
pub error_message_type: Option<String>,
/// Always returns 'Error' to indicate that this message is an error.
///
/// Example: "Error"
pub message_type: String,
pub message_version: Option<String>,
#[serde(rename = "sdkTransID")]
Expand Down Expand Up @@ -122,3 +125,112 @@ pub struct GpaymentsPreAuthenticationResponse {
#[serde(rename = "threeDSServerTransID")]
pub three_ds_server_trans_id: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GpaymentsAuthenticationRequest {
pub acct_number: CardNumber,
pub authentication_ind: String,
pub browser_info_collected: BrowserInfoCollected,
pub card_expiry_date: String,
#[serde(rename = "notificationURL")]
pub notification_url: String,
pub merchant_id: String,
#[serde(rename = "threeDSCompInd")]
pub three_ds_comp_ind: ThreeDsCompletionIndicator,
pub message_category: String,
pub purchase_amount: String,
pub purchase_date: String,
#[serde(rename = "threeDSServerTransID")]
pub three_ds_server_trans_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BrowserInfoCollected {
pub browser_accept_header: Option<String>,
pub browser_color_depth: Option<String>,
#[serde(rename = "browserIP")]
pub browser_ip: Option<Secret<String, common_utils::pii::IpAddress>>,
pub browser_javascript_enabled: Option<bool>,
pub browser_java_enabled: Option<bool>,
pub browser_language: Option<String>,
pub browser_screen_height: Option<String>,
pub browser_screen_width: Option<String>,
#[serde(rename = "browserTZ")]
pub browser_tz: Option<String>,
pub browser_user_agent: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AuthenticationInd {
#[serde(rename = "01")]
PaymentTransaction,
#[serde(rename = "02")]
RecurringTransaction,
#[serde(rename = "03")]
InstalmentTransaction,
#[serde(rename = "04")]
AddCard,
#[serde(rename = "05")]
MaintainCard,
#[serde(rename = "06")]
CardholderVerification,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GpaymentsAuthenticationSuccessResponse {
#[serde(rename = "dsReferenceNumber")]
pub ds_reference_number: String,
#[serde(rename = "dsTransID")]
pub ds_trans_id: String,
#[serde(rename = "threeDSServerTransID")]
pub three_ds_server_trans_id: String,
#[serde(rename = "messageVersion")]
pub message_version: String,
#[serde(rename = "transStatus")]
pub trans_status: AuthStatus,
#[serde(rename = "acsTransID")]
pub acs_trans_id: String,
#[serde(rename = "challengeUrl")]
pub acs_url: Option<url::Url>,
#[serde(rename = "acsReferenceNumber")]
pub acs_reference_number: String,
pub authentication_value: Option<String>,
}

#[derive(Deserialize, Debug, Clone, Serialize, PartialEq)]
pub enum AuthStatus {
/// Authentication/ Account Verification Successful
Y,
/// Not Authenticated /Account Not Verified; Transaction denied
N,
/// Authentication/ Account Verification Could Not Be Performed; Technical or other problem, as indicated in ARes or RReq
U,
/// Attempts Processing Performed; Not Authenticated/Verified , but a proof of attempted authentication/verification is provided
A,
/// Authentication/ Account Verification Rejected; Issuer is rejecting authentication/verification and request that authorisation not be attempted.
R,
/// Challenge required
C,
}

impl From<AuthStatus> for common_enums::TransactionStatus {
fn from(value: AuthStatus) -> Self {
match value {
AuthStatus::Y => Self::Success,
AuthStatus::N => Self::Failure,
AuthStatus::U => Self::VerificationNotPerformed,
AuthStatus::A => Self::NotVerified,
AuthStatus::R => Self::Rejected,
AuthStatus::C => Self::ChallengeRequired,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GpaymentsPostAuthenticationResponse {
pub authentication_value: Option<String>,
pub trans_status: AuthStatus,
pub eci: Option<String>,
}
Loading

0 comments on commit d93f65f

Please sign in to comment.