feat(connector) : add Cards(3ds & non3ds),bank_redirects ,wallets(Paypal,Applepay) and Mandates support to nexinets (#898)

Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com>
This commit is contained in:
chikke srujan
2023-05-03 13:47:42 +05:30
committed by GitHub
parent 2cff019a1b
commit eea05f5c31
11 changed files with 1148 additions and 252 deletions

View File

@ -7,12 +7,14 @@ use transformers as nexinets;
use crate::{
configs::settings,
connector::utils::{to_connector_meta, PaymentsSyncRequestData},
core::errors::{self, CustomResult},
headers,
services::{self, ConnectorIntegration},
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
storage::enums,
ErrorResponse, Response,
},
utils::{self, BytesExt},
@ -33,6 +35,16 @@ impl api::Refund for Nexinets {}
impl api::RefundExecute for Nexinets {}
impl api::RefundSync for Nexinets {}
impl Nexinets {
pub fn connector_transaction_id(
&self,
connector_meta: &Option<serde_json::Value>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(connector_meta.clone())?;
Ok(meta.transaction_id)
}
}
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Nexinets
where
Self: ConnectorIntegration<Flow, Request, Response>,
@ -44,7 +56,7 @@ where
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
self.get_content_type().to_string(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
@ -83,27 +95,32 @@ impl ConnectorCommon for Nexinets {
.parse_struct("NexinetsErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let errors = response.errors.clone();
let mut message = String::new();
for error in errors.iter() {
let field = error.field.to_owned().unwrap_or_default();
let mut msg = String::new();
if !field.is_empty() {
msg.push_str(format!("{} : {}", field, error.message).as_str());
} else {
msg = error.message.to_owned();
}
if message.is_empty() {
message.push_str(&msg);
} else {
message.push_str(format!(", {}", msg).as_str());
}
}
Ok(ErrorResponse {
status_code: res.status_code,
code: response.code,
message: response.message,
reason: response.reason,
status_code: response.status,
code: response.code.to_string(),
message,
reason: Some(response.message),
})
}
}
impl api::PaymentToken for Nexinets {}
impl
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Nexinets
{
// Not Implemented (R)
}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Nexinets
{
@ -136,10 +153,15 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
fn get_url(
&self,
_req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let url = if req.request.capture_method == Some(enums::CaptureMethod::Automatic) {
format!("{}/orders/debit", self.base_url(connectors))
} else {
format!("{}/orders/preauth", self.base_url(connectors))
};
Ok(url)
}
fn get_request_body(
@ -178,7 +200,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
data: &types::PaymentsAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: nexinets::NexinetsPaymentsResponse = res
let response: nexinets::NexinetsPreAuthOrDebitResponse = res
.response
.parse_struct("Nexinets PaymentsAuthorizeResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -187,7 +209,6 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -215,10 +236,23 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
fn get_url(
&self,
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let meta: nexinets::NexinetsPaymentsMetadata =
to_connector_meta(req.request.connector_meta.clone())?;
let order_id = nexinets::get_order_id(&meta)?;
let transaction_id = match meta.psync_flow {
transformers::NexinetsTransactionType::Debit
| transformers::NexinetsTransactionType::Capture => {
req.request.get_connector_transaction_id()?
}
_ => nexinets::get_transaction_id(&meta)?,
};
Ok(format!(
"{}/orders/{order_id}/transactions/{transaction_id}",
self.base_url(connectors)
))
}
fn build_request(
@ -241,16 +275,15 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
data: &types::PaymentsSyncRouterData,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: nexinets::NexinetsPaymentsResponse = res
let response: nexinets::NexinetsPaymentResponse = res
.response
.parse_struct("nexinets PaymentsSyncResponse")
.parse_struct("nexinets NexinetsPaymentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -278,17 +311,30 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
fn get_url(
&self,
_req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let meta: nexinets::NexinetsPaymentsMetadata =
to_connector_meta(req.request.connector_meta.clone())?;
let order_id = nexinets::get_order_id(&meta)?;
let transaction_id = nexinets::get_transaction_id(&meta)?;
Ok(format!(
"{}/orders/{order_id}/transactions/{transaction_id}/capture",
self.base_url(connectors)
))
}
fn get_request_body(
&self,
_req: &types::PaymentsCaptureRouterData,
req: &types::PaymentsCaptureRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into())
let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?;
let nexinets_req =
utils::Encode::<nexinets::NexinetsCaptureOrVoidRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(nexinets_req))
}
fn build_request(
@ -304,6 +350,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
.headers(types::PaymentsCaptureType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
.build(),
))
}
@ -313,16 +360,15 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
data: &types::PaymentsCaptureRouterData,
res: Response,
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
let response: nexinets::NexinetsPaymentsResponse = res
let response: nexinets::NexinetsPaymentResponse = res
.response
.parse_struct("Nexinets PaymentsCaptureResponse")
.parse_struct("NexinetsPaymentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -336,6 +382,83 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Nexinets
{
fn get_headers(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, 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::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let meta: nexinets::NexinetsPaymentsMetadata =
to_connector_meta(req.request.connector_meta.clone())?;
let order_id = nexinets::get_order_id(&meta)?;
let transaction_id = nexinets::get_transaction_id(&meta)?;
Ok(format!(
"{}/orders/{order_id}/transactions/{transaction_id}/cancel",
self.base_url(connectors),
))
}
fn get_request_body(
&self,
req: &types::PaymentsCancelRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?;
let nexinets_req =
utils::Encode::<nexinets::NexinetsCaptureOrVoidRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(nexinets_req))
}
fn build_request(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let request = services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
.body(types::PaymentsVoidType::get_request_body(self, req)?)
.build();
Ok(Some(request))
}
fn handle_response(
&self,
data: &types::PaymentsCancelRouterData,
res: Response,
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
let response: nexinets::NexinetsPaymentResponse = res
.response
.parse_struct("NexinetsPaymentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
@ -355,10 +478,17 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
fn get_url(
&self,
_req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let meta: nexinets::NexinetsPaymentsMetadata =
to_connector_meta(req.request.connector_metadata.clone())?;
let order_id = nexinets::get_order_id(&meta)?;
Ok(format!(
"{}/orders/{order_id}/transactions/{}/refund",
self.base_url(connectors),
req.request.connector_transaction_id
))
}
fn get_request_body(
@ -394,7 +524,7 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
data: &types::RefundsRouterData<api::Execute>,
res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
let response: nexinets::RefundResponse = res
let response: nexinets::NexinetsRefundResponse = res
.response
.parse_struct("nexinets RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -403,7 +533,6 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -429,10 +558,21 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
fn get_url(
&self,
_req: &types::RefundSyncRouterData,
_connectors: &settings::Connectors,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let transaction_id = req
.request
.connector_refund_id
.clone()
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
let meta: nexinets::NexinetsPaymentsMetadata =
to_connector_meta(req.request.connector_metadata.clone())?;
let order_id = nexinets::get_order_id(&meta)?;
Ok(format!(
"{}/orders/{order_id}/transactions/{transaction_id}",
self.base_url(connectors)
))
}
fn build_request(
@ -446,7 +586,6 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.body(types::RefundSyncType::get_request_body(self, req)?)
.build(),
))
}
@ -456,7 +595,7 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
data: &types::RefundSyncRouterData,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: nexinets::RefundResponse = res
let response: nexinets::NexinetsRefundResponse = res
.response
.parse_struct("nexinets RefundSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -465,7 +604,6 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
@ -499,3 +637,15 @@ impl api::IncomingWebhook for Nexinets {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
}
impl api::PaymentToken for Nexinets {}
impl
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Nexinets
{
// Not Implemented (R)
}

View File

@ -1,48 +1,184 @@
use api_models::payments::PaymentMethodData;
use base64::Engine;
use common_utils::errors::CustomResult;
use error_stack::{IntoReport, ResultExt};
use masking::Secret;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
connector::utils::PaymentsAuthorizeRequestData,
connector::utils::{
CardData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, WalletData,
},
consts,
core::errors,
types::{self, api, storage::enums},
services,
types::{self, api, storage::enums, transformers::ForeignFrom},
};
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPaymentsRequest {
amount: i64,
card: NexinetsCard,
initial_amount: i64,
currency: enums::Currency,
channel: NexinetsChannel,
product: NexinetsProduct,
payment: Option<NexinetsPaymentDetails>,
#[serde(rename = "async")]
nexinets_async: NexinetsAsyncDetails,
}
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct NexinetsCard {
name: Secret<String>,
number: Secret<String, common_utils::pii::CardNumber>,
#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsChannel {
#[default]
Ecom,
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum NexinetsProduct {
#[default]
Creditcard,
Paypal,
Giropay,
Sofort,
Eps,
Ideal,
Applepay,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum NexinetsPaymentDetails {
Card(Box<NexiCardDetails>),
Wallet(Box<NexinetsWalletDetails>),
BankRedirects(Box<NexinetsBankRedirects>),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexiCardDetails {
#[serde(flatten)]
card_data: CardDataDetails,
cof_contract: Option<CofContract>,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum CardDataDetails {
CardDetails(Box<CardDetails>),
PaymentInstrument(Box<PaymentInstrument>),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardDetails {
card_number: Secret<String, common_utils::pii::CardNumber>,
expiry_month: Secret<String>,
expiry_year: Secret<String>,
cvc: Secret<String>,
complete: bool,
verification: Secret<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentInstrument {
payment_instrument_id: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct CofContract {
#[serde(rename = "type")]
recurring_type: RecurringType,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RecurringType {
Unscheduled,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsBankRedirects {
bic: NexinetsBIC,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsAsyncDetails {
pub success_url: Option<String>,
pub cancel_url: Option<String>,
pub failure_url: Option<String>,
}
#[derive(Debug, Serialize)]
pub enum NexinetsBIC {
#[serde(rename = "ABNANL2A")]
AbnAmro,
#[serde(rename = "ASNBNL21")]
AsnBank,
#[serde(rename = "BUNQNL2A")]
Bunq,
#[serde(rename = "INGBNL2A")]
Ing,
#[serde(rename = "KNABNL2H")]
Knab,
#[serde(rename = "RABONL2U")]
Rabobank,
#[serde(rename = "RBRBNL21")]
Regiobank,
#[serde(rename = "SNSBNL2A")]
SnsBank,
#[serde(rename = "TRIONL2U")]
TriodosBank,
#[serde(rename = "FVLBNL22")]
VanLanschot,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum NexinetsWalletDetails {
ApplePayToken(Box<ApplePayDetails>),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayDetails {
payment_data: serde_json::Value,
payment_method: ApplepayPaymentMethod,
transaction_identifier: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepayPaymentMethod {
display_name: String,
network: String,
#[serde(rename = "type")]
token_type: String,
}
impl TryFrom<&types::PaymentsAuthorizeRouterData> for NexinetsPaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
match item.request.payment_method_data.clone() {
api::PaymentMethodData::Card(req_card) => {
let card = NexinetsCard {
name: req_card.card_holder_name,
number: req_card.card_number,
expiry_month: req_card.card_exp_month,
expiry_year: req_card.card_exp_year,
cvc: req_card.card_cvc,
complete: item.request.is_auto_capture()?,
};
Ok(Self {
amount: item.request.amount,
card,
})
}
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
}
let return_url = item.request.router_return_url.clone();
let nexinets_async = NexinetsAsyncDetails {
success_url: return_url.clone(),
cancel_url: return_url.clone(),
failure_url: return_url,
};
let (payment, product) = get_payment_details_and_product(item)?;
Ok(Self {
initial_amount: item.request.amount,
currency: item.request.currency,
channel: NexinetsChannel::Ecom,
product,
payment,
nexinets_async,
})
}
}
@ -55,60 +191,251 @@ impl TryFrom<&types::ConnectorAuthType> for NexinetsAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
api_key: api_key.to_string(),
}),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
types::ConnectorAuthType::BodyKey { api_key, key1 } => {
let auth_key = format!("{key1}:{api_key}");
let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key));
Ok(Self {
api_key: auth_header,
})
}
_ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
}
}
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsPaymentStatus {
Succeeded,
Failed,
#[default]
Processing,
Success,
Pending,
Ok,
Failure,
Declined,
InProgress,
Expired,
Aborted,
}
impl From<NexinetsPaymentStatus> for enums::AttemptStatus {
fn from(item: NexinetsPaymentStatus) -> Self {
match item {
NexinetsPaymentStatus::Succeeded => Self::Charged,
NexinetsPaymentStatus::Failed => Self::Failure,
NexinetsPaymentStatus::Processing => Self::Authorizing,
impl ForeignFrom<(NexinetsPaymentStatus, NexinetsTransactionType)> for enums::AttemptStatus {
fn foreign_from((status, method): (NexinetsPaymentStatus, NexinetsTransactionType)) -> Self {
match status {
NexinetsPaymentStatus::Success => match method {
NexinetsTransactionType::Preauth => Self::Authorized,
NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => Self::Charged,
NexinetsTransactionType::Cancel => Self::Voided,
},
NexinetsPaymentStatus::Declined
| NexinetsPaymentStatus::Failure
| NexinetsPaymentStatus::Expired
| NexinetsPaymentStatus::Aborted => match method {
NexinetsTransactionType::Preauth => Self::AuthorizationFailed,
NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
Self::CaptureFailed
}
NexinetsTransactionType::Cancel => Self::VoidFailed,
},
NexinetsPaymentStatus::Ok => match method {
NexinetsTransactionType::Preauth => Self::Authorized,
_ => Self::Pending,
},
NexinetsPaymentStatus::Pending => Self::AuthenticationPending,
NexinetsPaymentStatus::InProgress => Self::Pending,
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NexinetsPaymentsResponse {
status: NexinetsPaymentStatus,
id: String,
impl TryFrom<&api_models::enums::BankNames> for NexinetsBIC {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(bank: &api_models::enums::BankNames) -> Result<Self, Self::Error> {
match bank {
api_models::enums::BankNames::AbnAmro => Ok(Self::AbnAmro),
api_models::enums::BankNames::AsnBank => Ok(Self::AsnBank),
api_models::enums::BankNames::Bunq => Ok(Self::Bunq),
api_models::enums::BankNames::Ing => Ok(Self::Ing),
api_models::enums::BankNames::Knab => Ok(Self::Knab),
api_models::enums::BankNames::Rabobank => Ok(Self::Rabobank),
api_models::enums::BankNames::Regiobank => Ok(Self::Regiobank),
api_models::enums::BankNames::SnsBank => Ok(Self::SnsBank),
api_models::enums::BankNames::TriodosBank => Ok(Self::TriodosBank),
api_models::enums::BankNames::VanLanschot => Ok(Self::VanLanschot),
_ => Err(errors::ConnectorError::FlowNotSupported {
flow: bank.to_string(),
connector: "Nexinets".to_string(),
}
.into()),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPreAuthOrDebitResponse {
order_id: String,
transaction_type: NexinetsTransactionType,
transactions: Vec<NexinetsTransaction>,
payment_instrument: PaymentInstrument,
redirect_url: Option<Url>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsTransaction {
pub transaction_id: String,
#[serde(rename = "type")]
pub transaction_type: NexinetsTransactionType,
pub currency: enums::Currency,
pub status: NexinetsPaymentStatus,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsTransactionType {
Preauth,
Debit,
Capture,
Cancel,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NexinetsPaymentsMetadata {
pub transaction_id: Option<String>,
pub order_id: Option<String>,
pub psync_flow: NexinetsTransactionType,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, NexinetsPaymentsResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
TryFrom<
types::ResponseRouterData<
F,
NexinetsPreAuthOrDebitResponse,
T,
types::PaymentsResponseData,
>,
> for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
NexinetsPaymentsResponse,
NexinetsPreAuthOrDebitResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let transaction = match item.response.transactions.first() {
Some(order) => order,
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
};
let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata {
transaction_id: Some(transaction.transaction_id.clone()),
order_id: Some(item.response.order_id.clone()),
psync_flow: item.response.transaction_type.clone(),
})
.into_report()
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
let redirection_data = item
.response
.redirect_url
.map(|url| services::RedirectForm::from((url, services::Method::Get)));
let resource_id = match item.response.transaction_type.clone() {
NexinetsTransactionType::Preauth => types::ResponseId::NoResponseId,
NexinetsTransactionType::Debit => {
types::ResponseId::ConnectorTransactionId(transaction.transaction_id.clone())
}
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
};
let mandate_reference = item.response.payment_instrument.payment_instrument_id;
Ok(Self {
status: enums::AttemptStatus::from(item.response.status),
status: enums::AttemptStatus::foreign_from((
transaction.status.clone(),
item.response.transaction_type,
)),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
resource_id,
redirection_data,
mandate_reference,
connector_metadata: Some(connector_metadata),
}),
..item.data
})
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsCaptureOrVoidRequest {
pub initial_amount: i64,
pub currency: enums::Currency,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsOrder {
pub order_id: String,
}
impl TryFrom<&types::PaymentsCaptureRouterData> for NexinetsCaptureOrVoidRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
Ok(Self {
initial_amount: item.request.amount_to_capture,
currency: item.request.currency,
})
}
}
impl TryFrom<&types::PaymentsCancelRouterData> for NexinetsCaptureOrVoidRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
Ok(Self {
initial_amount: item.request.get_amount()?,
currency: item.request.get_currency()?,
})
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPaymentResponse {
pub transaction_id: String,
pub status: NexinetsPaymentStatus,
pub order: NexinetsOrder,
#[serde(rename = "type")]
pub transaction_type: NexinetsTransactionType,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, NexinetsPaymentResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, NexinetsPaymentResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let transaction_id = Some(item.response.transaction_id.clone());
let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata {
transaction_id,
order_id: Some(item.response.order.order_id),
psync_flow: item.response.transaction_type.clone(),
})
.into_report()
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
let resource_id = match item.response.transaction_type.clone() {
NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
types::ResponseId::ConnectorTransactionId(item.response.transaction_id)
}
_ => types::ResponseId::NoResponseId,
};
Ok(Self {
status: enums::AttemptStatus::foreign_from((
item.response.status,
item.response.transaction_type,
)),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
connector_metadata: Some(connector_metadata),
}),
..item.data
})
@ -117,57 +444,71 @@ impl<F, T>
// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsRefundRequest {
pub amount: i64,
pub initial_amount: i64,
pub currency: enums::Currency,
}
impl<F> TryFrom<&types::RefundsRouterData<F>> for NexinetsRefundRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
Ok(Self {
amount: item.request.amount,
initial_amount: item.request.refund_amount,
currency: item.request.currency,
})
}
}
// Type definition for Refund Response
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsRefundResponse {
pub transaction_id: String,
pub status: RefundStatus,
pub order: NexinetsOrder,
#[serde(rename = "type")]
pub transaction_type: RefundType,
}
#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
Succeeded,
Failed,
#[default]
Processing,
Success,
Ok,
Failure,
Declined,
InProgress,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundType {
Refund,
}
impl From<RefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self {
match item {
RefundStatus::Succeeded => Self::Success,
RefundStatus::Failed => Self::Failure,
RefundStatus::Processing => Self::Pending,
RefundStatus::Success => Self::Success,
RefundStatus::Failure | RefundStatus::Declined => Self::Failure,
RefundStatus::InProgress | RefundStatus::Ok => Self::Pending,
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
id: String,
status: RefundStatus,
}
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
impl TryFrom<types::RefundsResponseRouterData<api::Execute, NexinetsRefundResponse>>
for types::RefundsRouterData<api::Execute>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
item: types::RefundsResponseRouterData<api::Execute, NexinetsRefundResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(),
connector_refund_id: item.response.transaction_id,
refund_status: enums::RefundStatus::from(item.response.status),
}),
..item.data
@ -175,16 +516,16 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
}
}
impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
impl TryFrom<types::RefundsResponseRouterData<api::RSync, NexinetsRefundResponse>>
for types::RefundsRouterData<api::RSync>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
item: types::RefundsResponseRouterData<api::RSync, NexinetsRefundResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(),
connector_refund_id: item.response.transaction_id,
refund_status: enums::RefundStatus::from(item.response.status),
}),
..item.data
@ -192,10 +533,153 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Deserialize)]
pub struct NexinetsErrorResponse {
pub status_code: u16,
pub code: String,
pub status: u16,
pub code: u16,
pub message: String,
pub reason: Option<String>,
pub errors: Vec<OrderErrorDetails>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct OrderErrorDetails {
pub code: u16,
pub message: String,
pub field: Option<String>,
}
fn get_payment_details_and_product(
item: &types::PaymentsAuthorizeRouterData,
) -> Result<
(Option<NexinetsPaymentDetails>, NexinetsProduct),
error_stack::Report<errors::ConnectorError>,
> {
match &item.request.payment_method_data {
PaymentMethodData::Card(card) => Ok((
Some(get_card_data(item, card)?),
NexinetsProduct::Creditcard,
)),
PaymentMethodData::Wallet(wallet) => Ok(get_wallet_details(wallet)?),
PaymentMethodData::BankRedirect(bank_redirect) => match bank_redirect {
api_models::payments::BankRedirectData::Eps { .. } => Ok((None, NexinetsProduct::Eps)),
api_models::payments::BankRedirectData::Giropay { .. } => {
Ok((None, NexinetsProduct::Giropay))
}
api_models::payments::BankRedirectData::Ideal { bank_name, .. } => Ok((
Some(NexinetsPaymentDetails::BankRedirects(Box::new(
NexinetsBankRedirects {
bic: NexinetsBIC::try_from(bank_name)?,
},
))),
NexinetsProduct::Ideal,
)),
api_models::payments::BankRedirectData::Sofort { .. } => {
Ok((None, NexinetsProduct::Sofort))
}
_ => Err(errors::ConnectorError::NotImplemented(
"Payment methods".to_string(),
))?,
},
_ => Err(errors::ConnectorError::NotImplemented(
"Payment methods".to_string(),
))?,
}
}
fn get_card_data(
item: &types::PaymentsAuthorizeRouterData,
card: &api_models::payments::Card,
) -> Result<NexinetsPaymentDetails, errors::ConnectorError> {
let (card_data, cof_contract) = match item.request.is_mandate_payment() {
true => {
let card_data = match item.request.off_session {
Some(true) => CardDataDetails::PaymentInstrument(Box::new(PaymentInstrument {
payment_instrument_id: item.request.connector_mandate_id(),
})),
_ => CardDataDetails::CardDetails(Box::new(get_card_details(card))),
};
let cof_contract = Some(CofContract {
recurring_type: RecurringType::Unscheduled,
});
(card_data, cof_contract)
}
false => (
CardDataDetails::CardDetails(Box::new(get_card_details(card))),
None,
),
};
Ok(NexinetsPaymentDetails::Card(Box::new(NexiCardDetails {
card_data,
cof_contract,
})))
}
fn get_applepay_details(
wallet_data: &api_models::payments::WalletData,
applepay_data: &api_models::payments::ApplePayWalletData,
) -> CustomResult<ApplePayDetails, errors::ConnectorError> {
let payment_data = wallet_data.get_wallet_token_as_json()?;
Ok(ApplePayDetails {
payment_data,
payment_method: ApplepayPaymentMethod {
display_name: applepay_data.payment_method.display_name.to_owned(),
network: applepay_data.payment_method.network.to_owned(),
token_type: applepay_data.payment_method.pm_type.to_owned(),
},
transaction_identifier: applepay_data.transaction_identifier.to_owned(),
})
}
fn get_card_details(req_card: &api_models::payments::Card) -> CardDetails {
CardDetails {
card_number: req_card.card_number.clone(),
expiry_month: req_card.card_exp_month.clone(),
expiry_year: req_card.get_card_expiry_year_2_digit(),
verification: req_card.card_cvc.clone(),
}
}
fn get_wallet_details(
wallet: &api_models::payments::WalletData,
) -> Result<
(Option<NexinetsPaymentDetails>, NexinetsProduct),
error_stack::Report<errors::ConnectorError>,
> {
match wallet {
api_models::payments::WalletData::PaypalRedirect(_) => Ok((None, NexinetsProduct::Paypal)),
api_models::payments::WalletData::ApplePay(applepay_data) => Ok((
Some(NexinetsPaymentDetails::Wallet(Box::new(
NexinetsWalletDetails::ApplePayToken(Box::new(get_applepay_details(
wallet,
applepay_data,
)?)),
))),
NexinetsProduct::Applepay,
)),
_ => Err(errors::ConnectorError::NotImplemented(
"Payment methods".to_string(),
))?,
}
}
pub fn get_order_id(
meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
let order_id = meta.order_id.clone().ok_or(
errors::ConnectorError::MissingConnectorRelatedTransactionID {
id: "order_id".to_string(),
},
)?;
Ok(order_id)
}
pub fn get_transaction_id(
meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
let transaction_id = meta.transaction_id.clone().ok_or(
errors::ConnectorError::MissingConnectorRelatedTransactionID {
id: "transaction_id".to_string(),
},
)?;
Ok(transaction_id)
}

View File

@ -244,7 +244,7 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData {
pub trait PaymentsSyncRequestData {
fn is_auto_capture(&self) -> Result<bool, Error>;
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError>;
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ConnectorError>;
}
impl PaymentsSyncRequestData for types::PaymentsSyncData {
@ -255,14 +255,15 @@ impl PaymentsSyncRequestData for types::PaymentsSyncData {
Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()),
}
}
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError> {
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ConnectorError> {
match self.connector_transaction_id.clone() {
ResponseId::ConnectorTransactionId(txn_id) => Ok(txn_id),
_ => Err(errors::ValidationError::IncorrectValueProvided {
field_name: "connector_transaction_id",
})
.into_report()
.attach_printable("Expected connector transaction ID not found"),
.attach_printable("Expected connector transaction ID not found")
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?,
}
}
}
@ -400,7 +401,7 @@ impl WalletData for api::WalletData {
fn get_wallet_token(&self) -> Result<String, Error> {
match self {
Self::GooglePay(data) => Ok(data.tokenization_data.token.clone()),
Self::ApplePay(data) => Ok(data.payment_data.clone()),
Self::ApplePay(data) => Ok(data.get_applepay_decoded_payment_data()?),
Self::PaypalSdk(data) => Ok(data.token.clone()),
_ => Err(errors::ConnectorError::InvalidWallet.into()),
}
@ -415,6 +416,23 @@ impl WalletData for api::WalletData {
}
}
pub trait ApplePay {
fn get_applepay_decoded_payment_data(&self) -> Result<String, Error>;
}
impl ApplePay for payments::ApplePayWalletData {
fn get_applepay_decoded_payment_data(&self) -> Result<String, Error> {
let token = String::from_utf8(
consts::BASE64_ENGINE
.decode(&self.payment_data)
.into_report()
.change_context(errors::ConnectorError::InvalidWalletToken)?,
)
.into_report()
.change_context(errors::ConnectorError::InvalidWalletToken)?;
Ok(token)
}
}
pub trait PhoneDetailsData {
fn get_number(&self) -> Result<Secret<String>, Error>;
fn get_country_code(&self) -> Result<String, Error>;

View File

@ -295,6 +295,8 @@ pub enum ConnectorError {
MismatchedPaymentData,
#[error("Failed to parse Wallet token")]
InvalidWalletToken,
#[error("Missing Connector Related Transaction ID")]
MissingConnectorRelatedTransactionID { id: String },
#[error("File Validation failed")]
FileValidationFailed { reason: String },
}

View File

@ -177,7 +177,6 @@ default_imp_for_connector_request_id!(
connector::Klarna,
connector::Mollie,
connector::Multisafepay,
connector::Nexinets,
connector::Nuvei,
connector::Opennode,
connector::Payeezy,

View File

@ -6,7 +6,7 @@ use router_env::{instrument, tracing};
use super::{flows::Feature, PaymentAddress, PaymentData};
use crate::{
configs::settings::Server,
connector::Paypal,
connector::{Nexinets, Paypal},
core::{
errors::{self, RouterResponse, RouterResult},
payments::{self, helpers},
@ -581,6 +581,16 @@ impl api::ConnectorTransactionId for Paypal {
}
}
impl api::ConnectorTransactionId for Nexinets {
fn connector_transaction_id(
&self,
payment_attempt: storage::PaymentAttempt,
) -> Result<Option<String>, errors::ApiErrorResponse> {
let metadata = Self::connector_transaction_id(self, &payment_attempt.connector_metadata);
metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound)
}
}
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCaptureData {
type Error = error_stack::Report<errors::ApiErrorResponse>;

View File

@ -218,7 +218,7 @@ impl ConnectorData {
"worldline" => Ok(Box::new(&connector::Worldline)),
"worldpay" => Ok(Box::new(&connector::Worldpay)),
"multisafepay" => Ok(Box::new(&connector::Multisafepay)),
// "nexinets" => Ok(Box::new(&connector::Nexinets)), added as template code for future use
"nexinets" => Ok(Box::new(&connector::Nexinets)),
"paypal" => Ok(Box::new(&connector::Paypal)),
"trustpay" => Ok(Box::new(&connector::Trustpay)),
"zen" => Ok(Box::new(&connector::Zen)),