mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
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:
@ -303,6 +303,7 @@ impl
|
||||
.headers(types::PaymentsCaptureType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
@ -601,7 +601,7 @@ pub enum Connector {
|
||||
Klarna,
|
||||
Mollie,
|
||||
Multisafepay,
|
||||
// Nexinets, added as template code for future use
|
||||
Nexinets,
|
||||
Nuvei,
|
||||
// Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage
|
||||
Paypal,
|
||||
@ -666,7 +666,7 @@ pub enum RoutableConnectors {
|
||||
Klarna,
|
||||
Mollie,
|
||||
Multisafepay,
|
||||
// Nexinets, added as template code for future use
|
||||
Nexinets,
|
||||
Nuvei,
|
||||
Opennode,
|
||||
// Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -1,49 +1,185 @@
|
||||
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()?,
|
||||
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 {
|
||||
amount: item.request.amount,
|
||||
card,
|
||||
initial_amount: item.request.amount,
|
||||
currency: item.request.currency,
|
||||
channel: NexinetsChannel::Ecom,
|
||||
product,
|
||||
payment,
|
||||
nexinets_async,
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auth Struct
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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 },
|
||||
}
|
||||
|
||||
@ -177,7 +177,6 @@ default_imp_for_connector_request_id!(
|
||||
connector::Klarna,
|
||||
connector::Mollie,
|
||||
connector::Multisafepay,
|
||||
connector::Nexinets,
|
||||
connector::Nuvei,
|
||||
connector::Opennode,
|
||||
connector::Payeezy,
|
||||
|
||||
@ -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>;
|
||||
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -20,7 +20,7 @@ pub(crate) struct ConnectorAuthentication {
|
||||
pub globalpay: Option<HeaderKey>,
|
||||
pub mollie: Option<HeaderKey>,
|
||||
pub multisafepay: Option<HeaderKey>,
|
||||
pub nexinets: Option<HeaderKey>,
|
||||
pub nexinets: Option<BodyKey>,
|
||||
pub nuvei: Option<SignatureKey>,
|
||||
pub opennode: Option<HeaderKey>,
|
||||
pub payeezy: Option<SignatureKey>,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use masking::Secret;
|
||||
use router::types::{self, api, storage::enums};
|
||||
use router::types::{self, api, storage::enums, PaymentsAuthorizeData};
|
||||
|
||||
use crate::{
|
||||
connector_auth,
|
||||
@ -9,12 +9,13 @@ use crate::{
|
||||
#[derive(Clone, Copy)]
|
||||
struct NexinetsTest;
|
||||
impl ConnectorActions for NexinetsTest {}
|
||||
static CONNECTOR: NexinetsTest = NexinetsTest {};
|
||||
impl utils::Connector for NexinetsTest {
|
||||
fn get_data(&self) -> types::api::ConnectorData {
|
||||
use router::connector::Nexinets;
|
||||
types::api::ConnectorData {
|
||||
connector: Box::new(&Nexinets),
|
||||
connector_name: types::Connector::Dummy,
|
||||
connector_name: types::Connector::Nexinets,
|
||||
get_token: types::api::GetToken::Connector,
|
||||
}
|
||||
}
|
||||
@ -32,22 +33,23 @@ impl utils::Connector for NexinetsTest {
|
||||
}
|
||||
}
|
||||
|
||||
static CONNECTOR: NexinetsTest = NexinetsTest {};
|
||||
|
||||
fn get_default_payment_info() -> Option<utils::PaymentInfo> {
|
||||
None
|
||||
fn payment_method_details() -> Option<PaymentsAuthorizeData> {
|
||||
Some(PaymentsAuthorizeData {
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new("4012001038443335".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
router_return_url: Some("https://google.com".to_string()),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
})
|
||||
}
|
||||
|
||||
fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
None
|
||||
}
|
||||
|
||||
// Cards Positive Tests
|
||||
// Creates a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_only_authorize_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), get_default_payment_info())
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
@ -57,47 +59,66 @@ async fn should_only_authorize_payment() {
|
||||
#[actix_web::test]
|
||||
async fn should_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
.unwrap();
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
let connector_payment_id = "".to_string();
|
||||
let connector_meta = utils::get_connector_metadata(response.response);
|
||||
let capture_data = types::PaymentsCaptureData {
|
||||
connector_meta,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
};
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment(connector_payment_id, Some(capture_data), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(capture_response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Partially captures a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(
|
||||
payment_method_details(),
|
||||
Some(types::PaymentsCaptureData {
|
||||
amount_to_capture: 50,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
.unwrap();
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
let connector_payment_id = "".to_string();
|
||||
let connector_meta = utils::get_connector_metadata(response.response);
|
||||
let capture_data = types::PaymentsCaptureData {
|
||||
connector_meta,
|
||||
amount_to_capture: 50,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
};
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment(connector_payment_id, Some(capture_data), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(capture_response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Synchronizes a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_authorized_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), get_default_payment_info())
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
let txn_id = "".to_string();
|
||||
let connector_meta = utils::get_connector_metadata(authorize_response.response);
|
||||
let response = CONNECTOR
|
||||
.psync_retry_till_status_matches(
|
||||
enums::AttemptStatus::Authorized,
|
||||
Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||
txn_id.unwrap(),
|
||||
),
|
||||
..Default::default()
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(txn_id),
|
||||
encoded_data: None,
|
||||
capture_method: None,
|
||||
connector_meta,
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("PSync response");
|
||||
@ -108,29 +129,63 @@ async fn should_sync_authorized_payment() {
|
||||
#[actix_web::test]
|
||||
async fn should_void_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_void_payment(
|
||||
payment_method_details(),
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
let connector_payment_id = "".to_string();
|
||||
let connector_meta = utils::get_connector_metadata(response.response);
|
||||
let response = CONNECTOR
|
||||
.void_payment(
|
||||
connector_payment_id,
|
||||
Some(types::PaymentsCancelData {
|
||||
connector_transaction_id: String::from(""),
|
||||
cancellation_reason: Some("requested_by_customer".to_string()),
|
||||
..Default::default()
|
||||
connector_meta,
|
||||
amount: Some(100),
|
||||
currency: Some(storage_models::enums::Currency::EUR),
|
||||
..utils::PaymentCancelType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Void payment response");
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status, enums::AttemptStatus::Voided);
|
||||
}
|
||||
|
||||
// Refunds a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_manually_captured_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = "".to_string();
|
||||
let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment(
|
||||
txn_id,
|
||||
Some(types::PaymentsCaptureData {
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_meta: capture_connector_meta,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
let capture_txn_id =
|
||||
utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
|
||||
let refund_connector_metadata = utils::get_connector_metadata(capture_response.response);
|
||||
let response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
payment_method_details(),
|
||||
.refund_payment(
|
||||
capture_txn_id.clone(),
|
||||
Some(types::RefundsData {
|
||||
connector_transaction_id: capture_txn_id,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_metadata: refund_connector_metadata,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -143,15 +198,38 @@ async fn should_refund_manually_captured_payment() {
|
||||
// Partially refunds a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_refund_manually_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
payment_method_details(),
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = "".to_string();
|
||||
let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment(
|
||||
txn_id.clone(),
|
||||
Some(types::PaymentsCaptureData {
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_meta: capture_connector_meta,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
let capture_txn_id =
|
||||
utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
|
||||
let refund_connector_metadata = utils::get_connector_metadata(capture_response.response);
|
||||
let response = CONNECTOR
|
||||
.refund_payment(
|
||||
capture_txn_id.clone(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
refund_amount: 10,
|
||||
connector_transaction_id: capture_txn_id,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_metadata: refund_connector_metadata,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -164,21 +242,63 @@ async fn should_partially_refund_manually_captured_payment() {
|
||||
// Synchronizes a refund using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_manually_captured_refund() {
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), None)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = "".to_string();
|
||||
let capture_connector_meta = utils::get_connector_metadata(authorize_response.response);
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment(
|
||||
txn_id.clone(),
|
||||
Some(types::PaymentsCaptureData {
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_meta: capture_connector_meta,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
let capture_txn_id =
|
||||
utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
|
||||
let refund_connector_metadata = utils::get_connector_metadata(capture_response.response);
|
||||
let refund_response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
payment_method_details(),
|
||||
.refund_payment(
|
||||
capture_txn_id.clone(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 100,
|
||||
connector_transaction_id: capture_txn_id.clone(),
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_metadata: refund_connector_metadata.clone(),
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let transaction_id = Some(
|
||||
refund_response
|
||||
.response
|
||||
.clone()
|
||||
.unwrap()
|
||||
.connector_refund_id,
|
||||
);
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
refund_response
|
||||
.response
|
||||
.clone()
|
||||
.unwrap()
|
||||
.connector_refund_id,
|
||||
Some(types::RefundsData {
|
||||
connector_refund_id: transaction_id,
|
||||
connector_transaction_id: capture_txn_id,
|
||||
connector_metadata: refund_connector_metadata,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -192,7 +312,7 @@ async fn should_sync_manually_captured_refund() {
|
||||
#[actix_web::test]
|
||||
async fn should_make_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
@ -201,24 +321,23 @@ async fn should_make_payment() {
|
||||
// Synchronizes a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_auto_captured_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
let cap_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
assert_ne!(txn_id, None, "Empty connector transaction id");
|
||||
assert_eq!(cap_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(cap_response.response.clone()).unwrap();
|
||||
let connector_meta = utils::get_connector_metadata(cap_response.response);
|
||||
let response = CONNECTOR
|
||||
.psync_retry_till_status_matches(
|
||||
enums::AttemptStatus::Charged,
|
||||
Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||
txn_id.unwrap(),
|
||||
),
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(txn_id),
|
||||
capture_method: Some(enums::CaptureMethod::Automatic),
|
||||
connector_meta,
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -228,8 +347,25 @@ async fn should_sync_auto_captured_payment() {
|
||||
// Refunds a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_auto_captured_payment() {
|
||||
let captured_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(captured_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(captured_response.response.clone());
|
||||
let connector_metadata = utils::get_connector_metadata(captured_response.response);
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
|
||||
.refund_payment(
|
||||
txn_id.clone().unwrap(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 100,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_transaction_id: txn_id.unwrap(),
|
||||
connector_metadata,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -241,14 +377,55 @@ async fn should_refund_auto_captured_payment() {
|
||||
// Partially refunds a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_refund_succeeded_payment() {
|
||||
let refund_response = CONNECTOR
|
||||
.make_payment_and_refund(
|
||||
payment_method_details(),
|
||||
let captured_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(captured_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(captured_response.response.clone());
|
||||
let connector_meta = utils::get_connector_metadata(captured_response.response);
|
||||
let response = CONNECTOR
|
||||
.refund_payment(
|
||||
txn_id.clone().unwrap(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_transaction_id: txn_id.unwrap(),
|
||||
connector_metadata: connector_meta,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_succeeded_payment_multiple_times() {
|
||||
let captured_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(captured_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(captured_response.response.clone());
|
||||
let connector_meta = utils::get_connector_metadata(captured_response.response);
|
||||
for _x in 0..2 {
|
||||
let refund_response = CONNECTOR
|
||||
.refund_payment(
|
||||
txn_id.clone().unwrap(),
|
||||
Some(types::RefundsData {
|
||||
connector_metadata: connector_meta.clone(),
|
||||
connector_transaction_id: txn_id.clone().unwrap(),
|
||||
refund_amount: 50,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -257,35 +434,54 @@ async fn should_partially_refund_succeeded_payment() {
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_succeeded_payment_multiple_times() {
|
||||
CONNECTOR
|
||||
.make_payment_and_multiple_refund(
|
||||
payment_method_details(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Synchronizes a refund using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_refund() {
|
||||
let refund_response = CONNECTOR
|
||||
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
|
||||
let captured_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(captured_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(captured_response.response.clone());
|
||||
let connector_metadata = utils::get_connector_metadata(captured_response.response).clone();
|
||||
let refund_response = CONNECTOR
|
||||
.refund_payment(
|
||||
txn_id.clone().unwrap(),
|
||||
Some(types::RefundsData {
|
||||
connector_transaction_id: txn_id.clone().unwrap(),
|
||||
refund_amount: 100,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_metadata: connector_metadata.clone(),
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let transaction_id = Some(
|
||||
refund_response
|
||||
.response
|
||||
.clone()
|
||||
.unwrap()
|
||||
.connector_refund_id,
|
||||
);
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
refund_response
|
||||
.response
|
||||
.clone()
|
||||
.unwrap()
|
||||
.connector_refund_id,
|
||||
Some(types::RefundsData {
|
||||
connector_refund_id: transaction_id,
|
||||
connector_transaction_id: txn_id.unwrap(),
|
||||
connector_metadata,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -301,20 +497,21 @@ async fn should_sync_refund() {
|
||||
async fn should_fail_payment_for_incorrect_card_number() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
Some(PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new("1234567891011".to_string()),
|
||||
card_number: Secret::new("12345678910112331".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card number is incorrect.".to_string(),
|
||||
"payment.cardNumber : Bad value for 'payment.cardNumber'. Expected: string of length in range 12 <=> 19 representing a valid creditcard number.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -323,21 +520,21 @@ async fn should_fail_payment_for_incorrect_card_number() {
|
||||
async fn should_fail_payment_for_empty_card_number() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
Some(PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new(String::from("")),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(
|
||||
x.message,
|
||||
"You passed an empty string for 'payment_method_data[card][number]'.",
|
||||
"payment.cardNumber : Bad value for 'payment.cardNumber'. Expected: string of length in range 12 <=> 19 representing a valid creditcard number.",
|
||||
);
|
||||
}
|
||||
|
||||
@ -346,20 +543,20 @@ async fn should_fail_payment_for_empty_card_number() {
|
||||
async fn should_fail_payment_for_incorrect_cvc() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
Some(PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_cvc: Secret::new("12345".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's security code is invalid.".to_string(),
|
||||
"payment.verification : Bad value for 'payment.verification'. Expected: string of length in range 3 <=> 4 representing a valid creditcard verification number.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -368,20 +565,20 @@ async fn should_fail_payment_for_incorrect_cvc() {
|
||||
async fn should_fail_payment_for_invalid_exp_month() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
Some(PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_exp_month: Secret::new("20".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's expiration month is invalid.".to_string(),
|
||||
"payment.expiryMonth : Bad value for 'payment.expiryMonth'. Expected: string of length 2 in range '01' <=> '12' representing the month in a valid creditcard expiry date >= current date.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -390,73 +587,108 @@ async fn should_fail_payment_for_invalid_exp_month() {
|
||||
async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
Some(PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_exp_year: Secret::new("2000".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's expiration year is invalid.".to_string(),
|
||||
"payment.expiryYear : Bad value for 'payment.expiryYear'. Expected: string of length 2 in range '01' <=> '99' representing the year in a valid creditcard expiry date >= current date.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Voids a payment using automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_fail_void_payment_for_auto_capture() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
let captured_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
assert_ne!(txn_id, None, "Empty connector transaction id");
|
||||
assert_eq!(captured_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(captured_response.response.clone()).unwrap();
|
||||
let connector_meta = utils::get_connector_metadata(captured_response.response);
|
||||
let void_response = CONNECTOR
|
||||
.void_payment(txn_id.unwrap(), None, get_default_payment_info())
|
||||
.void_payment(
|
||||
txn_id,
|
||||
Some(types::PaymentsCancelData {
|
||||
cancellation_reason: Some("requested_by_customer".to_string()),
|
||||
amount: Some(100),
|
||||
currency: Some(storage_models::enums::Currency::EUR),
|
||||
connector_meta,
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
void_response.response.unwrap_err().message,
|
||||
"You cannot cancel this PaymentIntent because it has a status of succeeded."
|
||||
"transactionId : Operation not allowed!"
|
||||
);
|
||||
}
|
||||
|
||||
// Captures a payment using invalid connector payment id.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_capture_for_invalid_payment() {
|
||||
let connector_payment_id = "".to_string();
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment("123456789".to_string(), None, get_default_payment_info())
|
||||
.capture_payment(
|
||||
connector_payment_id,
|
||||
Some(types::PaymentsCaptureData {
|
||||
connector_meta: Some(
|
||||
serde_json::json!({"transaction_id" : "transaction_usmh41hymb",
|
||||
"order_id" : "tjil1ymxsz",
|
||||
"psync_flow" : "PREAUTH"
|
||||
}),
|
||||
),
|
||||
amount_to_capture: 50,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
capture_response.response.unwrap_err().message,
|
||||
String::from("No such payment_intent: '123456789'")
|
||||
String::from("transactionId : Transaction does not belong to order.")
|
||||
);
|
||||
}
|
||||
|
||||
// Refunds a payment with refund amount higher than payment amount.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
|
||||
let captured_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(captured_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(captured_response.response.clone());
|
||||
let connector_meta = utils::get_connector_metadata(captured_response.response);
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(
|
||||
payment_method_details(),
|
||||
.refund_payment(
|
||||
txn_id.clone().unwrap(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 150,
|
||||
currency: storage_models::enums::Currency::EUR,
|
||||
connector_transaction_id: txn_id.unwrap(),
|
||||
connector_metadata: connector_meta,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Refund amount (₹1.50) is greater than charge amount (₹1.00)",
|
||||
"initialAmount : Bad value for 'initialAmount'. Expected: Positive integer between 1 and maximum available amount (debit/capture.initialAmount - debit/capture.refundedAmount.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user