mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(connector): implement auth and post auth flows for gpayments (#4746)
Co-authored-by: hrithikesh026 <hrithikesh.vm@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com>
This commit is contained in:
@ -9,6 +9,7 @@ use transformers as gpayments;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::settings,
|
configs::settings,
|
||||||
|
connector::{gpayments::gpayments_types::GpaymentsConnectorMetaData, utils::to_connector_meta},
|
||||||
core::errors::{self, CustomResult},
|
core::errors::{self, CustomResult},
|
||||||
events::connector_api_logs::ConnectorEvent,
|
events::connector_api_logs::ConnectorEvent,
|
||||||
headers, services,
|
headers, services,
|
||||||
@ -214,8 +215,103 @@ impl
|
|||||||
types::authentication::AuthenticationResponseData,
|
types::authentication::AuthenticationResponseData,
|
||||||
> for Gpayments
|
> 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
|
impl
|
||||||
ConnectorIntegration<
|
ConnectorIntegration<
|
||||||
api::PostAuthentication,
|
api::PostAuthentication,
|
||||||
@ -223,6 +319,92 @@ impl
|
|||||||
types::authentication::AuthenticationResponseData,
|
types::authentication::AuthenticationResponseData,
|
||||||
> for Gpayments
|
> 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
|
impl
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
|
use api_models::payments::ThreeDsCompletionIndicator;
|
||||||
use cards::CardNumber;
|
use cards::CardNumber;
|
||||||
use common_utils::types;
|
use common_utils::types;
|
||||||
use masking::{Deserialize, Serialize};
|
use masking::{Deserialize, Secret, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct GpaymentsConnectorMetaData {
|
pub struct GpaymentsConnectorMetaData {
|
||||||
pub authentication_url: String,
|
pub authentication_url: String,
|
||||||
pub three_ds_requestor_trans_id: Option<String>,
|
pub three_ds_requestor_trans_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GpaymentsPreAuthVersionCallRequest {
|
pub struct GpaymentsPreAuthVersionCallRequest {
|
||||||
@ -38,6 +38,9 @@ pub struct TDS2ApiError {
|
|||||||
pub error_description: String,
|
pub error_description: String,
|
||||||
pub error_detail: Option<String>,
|
pub error_detail: Option<String>,
|
||||||
pub error_message_type: 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_type: String,
|
||||||
pub message_version: Option<String>,
|
pub message_version: Option<String>,
|
||||||
#[serde(rename = "sdkTransID")]
|
#[serde(rename = "sdkTransID")]
|
||||||
@ -122,3 +125,112 @@ pub struct GpaymentsPreAuthenticationResponse {
|
|||||||
#[serde(rename = "threeDSServerTransID")]
|
#[serde(rename = "threeDSServerTransID")]
|
||||||
pub three_ds_server_trans_id: String,
|
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>,
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
|
use api_models::payments::DeviceChannel;
|
||||||
|
use base64::Engine;
|
||||||
|
use common_utils::date_time;
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use masking::Secret;
|
use masking::{ExposeInterface, Secret};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
use serde_json::to_string;
|
||||||
|
|
||||||
use super::gpayments_types;
|
use super::gpayments_types;
|
||||||
use crate::{
|
use crate::{
|
||||||
connector::utils,
|
connector::{
|
||||||
consts,
|
gpayments::gpayments_types::{
|
||||||
|
AuthStatus, BrowserInfoCollected, GpaymentsAuthenticationSuccessResponse,
|
||||||
|
},
|
||||||
|
utils,
|
||||||
|
utils::{get_card_details, CardData},
|
||||||
|
},
|
||||||
|
consts::BASE64_ENGINE,
|
||||||
core::errors,
|
core::errors,
|
||||||
types::{self, api},
|
types::{self, api, api::MessageCategory, authentication::ChallengeParams},
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: Fill the struct with respective fields
|
|
||||||
pub struct GpaymentsRouterData<T> {
|
pub struct GpaymentsRouterData<T> {
|
||||||
pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
|
pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
|
||||||
pub router_data: T,
|
pub router_data: T,
|
||||||
@ -26,7 +35,6 @@ impl<T> TryFrom<(i64, T)> for GpaymentsRouterData<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Fill the struct with respective fields
|
|
||||||
// Auth Struct
|
// Auth Struct
|
||||||
pub struct GpaymentsAuthType {
|
pub struct GpaymentsAuthType {
|
||||||
/// base64 encoded certificate
|
/// base64 encoded certificate
|
||||||
@ -68,15 +76,6 @@ impl TryFrom<&GpaymentsRouterData<&types::authentication::PreAuthNVersionCallRou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Fill the struct with respective fields
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct GpaymentsErrorResponse {
|
|
||||||
pub status_code: u16,
|
|
||||||
pub code: String,
|
|
||||||
pub message: String,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq)]
|
#[derive(Deserialize, PartialEq)]
|
||||||
pub struct GpaymentsMetaData {
|
pub struct GpaymentsMetaData {
|
||||||
pub endpoint_prefix: String,
|
pub endpoint_prefix: String,
|
||||||
@ -146,11 +145,9 @@ impl TryFrom<&GpaymentsRouterData<&types::authentication::PreAuthNRouterData>>
|
|||||||
acct_number: router_data.request.card_holder_account_number.clone(),
|
acct_number: router_data.request.card_holder_account_number.clone(),
|
||||||
card_scheme: None,
|
card_scheme: None,
|
||||||
challenge_window_size: Some(gpayments_types::ChallengeWindowSize::FullScreen),
|
challenge_window_size: Some(gpayments_types::ChallengeWindowSize::FullScreen),
|
||||||
// This is a required field but we don't listen to event callbacks
|
|
||||||
event_callback_url: "https://webhook.site/55e3db24-7c4e-4432-9941-d806f68d210b"
|
event_callback_url: "https://webhook.site/55e3db24-7c4e-4432-9941-d806f68d210b"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
merchant_id: metadata.merchant_id,
|
merchant_id: metadata.merchant_id,
|
||||||
// Since this feature is not in our favour, hard coded it to true
|
|
||||||
skip_auto_browser_info_collect: Some(true),
|
skip_auto_browser_info_collect: Some(true),
|
||||||
// should auto generate this id.
|
// should auto generate this id.
|
||||||
three_ds_requestor_trans_id: uuid::Uuid::new_v4().hyphenated().to_string(),
|
three_ds_requestor_trans_id: uuid::Uuid::new_v4().hyphenated().to_string(),
|
||||||
@ -158,6 +155,151 @@ impl TryFrom<&GpaymentsRouterData<&types::authentication::PreAuthNRouterData>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&GpaymentsRouterData<&types::authentication::ConnectorAuthenticationRouterData>>
|
||||||
|
for gpayments_types::GpaymentsAuthenticationRequest
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
item: &GpaymentsRouterData<&types::authentication::ConnectorAuthenticationRouterData>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let request = &item.router_data.request;
|
||||||
|
let browser_details = match request.browser_details.clone() {
|
||||||
|
Some(details) => Ok::<Option<types::BrowserInformation>, Self::Error>(Some(details)),
|
||||||
|
None => {
|
||||||
|
if request.device_channel == DeviceChannel::Browser {
|
||||||
|
Err(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "browser_info",
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
let card_details = get_card_details(request.payment_method_data.clone(), "gpayments")?;
|
||||||
|
|
||||||
|
let metadata = GpaymentsMetaData::try_from(&item.router_data.connector_meta_data)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
acct_number: card_details.card_number.clone(),
|
||||||
|
authentication_ind: "01".into(),
|
||||||
|
card_expiry_date: card_details.get_expiry_date_as_yymm()?.expose(),
|
||||||
|
merchant_id: metadata.merchant_id,
|
||||||
|
message_category: match item.router_data.request.message_category.clone() {
|
||||||
|
MessageCategory::Payment => "01".into(),
|
||||||
|
MessageCategory::NonPayment => "02".into(),
|
||||||
|
},
|
||||||
|
notification_url: request
|
||||||
|
.return_url
|
||||||
|
.clone()
|
||||||
|
.ok_or(errors::ConnectorError::RequestEncodingFailed)
|
||||||
|
.attach_printable("missing return_url")?,
|
||||||
|
three_ds_comp_ind: request.threeds_method_comp_ind.clone(),
|
||||||
|
purchase_amount: item.amount.to_string(),
|
||||||
|
purchase_date: date_time::DateTime::<date_time::YYYYMMDDHHmmss>::from(date_time::now())
|
||||||
|
.to_string(),
|
||||||
|
three_ds_server_trans_id: request
|
||||||
|
.pre_authentication_data
|
||||||
|
.threeds_server_transaction_id
|
||||||
|
.clone(),
|
||||||
|
browser_info_collected: BrowserInfoCollected {
|
||||||
|
browser_javascript_enabled: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.java_script_enabled),
|
||||||
|
browser_accept_header: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.accept_header.clone()),
|
||||||
|
browser_ip: browser_details
|
||||||
|
.clone()
|
||||||
|
.and_then(|details| details.ip_address.map(|ip| Secret::new(ip.to_string()))),
|
||||||
|
browser_java_enabled: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.java_enabled),
|
||||||
|
browser_language: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.language.clone()),
|
||||||
|
browser_color_depth: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.color_depth.map(|a| a.to_string())),
|
||||||
|
browser_screen_height: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.screen_height.map(|a| a.to_string())),
|
||||||
|
browser_screen_width: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.screen_width.map(|a| a.to_string())),
|
||||||
|
browser_tz: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.time_zone.map(|a| a.to_string())),
|
||||||
|
browser_user_agent: browser_details
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|details| details.user_agent.clone().map(|a| a.to_string())),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl
|
||||||
|
TryFrom<
|
||||||
|
types::ResponseRouterData<
|
||||||
|
api::Authentication,
|
||||||
|
GpaymentsAuthenticationSuccessResponse,
|
||||||
|
types::authentication::ConnectorAuthenticationRequestData,
|
||||||
|
types::authentication::AuthenticationResponseData,
|
||||||
|
>,
|
||||||
|
> for types::authentication::ConnectorAuthenticationRouterData
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::ResponseRouterData<
|
||||||
|
api::Authentication,
|
||||||
|
GpaymentsAuthenticationSuccessResponse,
|
||||||
|
types::authentication::ConnectorAuthenticationRequestData,
|
||||||
|
types::authentication::AuthenticationResponseData,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let response_auth = item.response;
|
||||||
|
let creq = serde_json::json!({
|
||||||
|
"threeDSServerTransID": response_auth.three_ds_server_trans_id,
|
||||||
|
"acsTransID": response_auth.acs_trans_id,
|
||||||
|
"messageVersion": response_auth.message_version,
|
||||||
|
"messageType": "CReq",
|
||||||
|
"challengeWindowSize": "01",
|
||||||
|
});
|
||||||
|
let creq_str = to_string(&creq)
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
|
||||||
|
.attach_printable("error while constructing creq_str")?;
|
||||||
|
let creq_base64 = Engine::encode(&BASE64_ENGINE, creq_str)
|
||||||
|
.trim_end_matches('=')
|
||||||
|
.to_owned();
|
||||||
|
let response: Result<
|
||||||
|
types::authentication::AuthenticationResponseData,
|
||||||
|
types::ErrorResponse,
|
||||||
|
> = Ok(
|
||||||
|
types::authentication::AuthenticationResponseData::AuthNResponse {
|
||||||
|
trans_status: response_auth.trans_status.clone().into(),
|
||||||
|
authn_flow_type: if response_auth.trans_status == AuthStatus::C {
|
||||||
|
types::authentication::AuthNFlowType::Challenge(Box::new(ChallengeParams {
|
||||||
|
acs_url: response_auth.acs_url,
|
||||||
|
challenge_request: Some(creq_base64),
|
||||||
|
acs_reference_number: Some(response_auth.acs_reference_number.clone()),
|
||||||
|
acs_trans_id: Some(response_auth.acs_trans_id.clone()),
|
||||||
|
three_dsserver_trans_id: Some(response_auth.three_ds_server_trans_id),
|
||||||
|
acs_signed_content: None,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
types::authentication::AuthNFlowType::Frictionless
|
||||||
|
},
|
||||||
|
authentication_value: response_auth.authentication_value,
|
||||||
|
ds_trans_id: Some(response_auth.ds_trans_id),
|
||||||
|
connector_metadata: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(Self {
|
||||||
|
response,
|
||||||
|
..item.data.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl
|
impl
|
||||||
TryFrom<
|
TryFrom<
|
||||||
types::ResponseRouterData<
|
types::ResponseRouterData<
|
||||||
@ -186,11 +328,11 @@ impl
|
|||||||
"threeDSServerTransID": threeds_method_response.three_ds_server_trans_id,
|
"threeDSServerTransID": threeds_method_response.three_ds_server_trans_id,
|
||||||
"threeDSMethodNotificationURL": "https://webhook.site/bd06863d-82c2-42ea-b35b-5ffd5ecece71"
|
"threeDSMethodNotificationURL": "https://webhook.site/bd06863d-82c2-42ea-b35b-5ffd5ecece71"
|
||||||
});
|
});
|
||||||
serde_json::to_string(&three_ds_method_data_json)
|
to_string(&three_ds_method_data_json)
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
|
||||||
.attach_printable("error while constructing three_ds_method_data_str")
|
.attach_printable("error while constructing three_ds_method_data_str")
|
||||||
.map(|three_ds_method_data_string| {
|
.map(|three_ds_method_data_string| {
|
||||||
base64::Engine::encode(&consts::BASE64_ENGINE, three_ds_method_data_string)
|
Engine::encode(&BASE64_ENGINE, three_ds_method_data_string)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user