feat(connector): [Nexixpay] add mandates flow for cards (#6259)

Co-authored-by: Mrudul Vajpayee <mrudul.vajpayee@mrudulvajpayee-XJWXCWP7HF.local>
This commit is contained in:
Mrudul Vajpayee
2024-12-05 14:16:04 +05:30
committed by GitHub
parent c2646d749c
commit 62521f367b
5 changed files with 981 additions and 150 deletions

View File

@ -1,4 +1,5 @@
pub mod transformers;
use std::collections::HashSet;
use common_enums::enums;
use common_utils::{
@ -9,6 +10,7 @@ use common_utils::{
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
payment_method_data::PaymentMethodData,
router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
router_flow_types::{
access_token_auth::AccessTokenAuth,
@ -46,7 +48,7 @@ use uuid::Uuid;
use crate::{
constants::headers,
types::ResponseRouterData,
utils::{self, RefundsRequestData},
utils::{self, PaymentMethodDataType, RefundsRequestData},
};
#[derive(Clone)]
@ -212,6 +214,15 @@ impl ConnectorValidation for Nexixpay {
),
}
}
fn validate_mandate_payment(
&self,
pm_type: Option<enums::PaymentMethodType>,
pm_data: PaymentMethodData,
) -> CustomResult<(), errors::ConnectorError> {
let mandate_supported_pmd: HashSet<PaymentMethodDataType> =
HashSet::from([PaymentMethodDataType::Card]);
utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
}
}
impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Nexixpay {}
@ -415,11 +426,15 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
fn get_url(
&self,
_req: &PaymentsAuthorizeRouterData,
req: &PaymentsAuthorizeRouterData,
connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
if req.request.off_session == Some(true) {
Ok(format!("{}/orders/mit", self.base_url(connectors)))
} else {
Ok(format!("{}/orders/3steps/init", self.base_url(connectors)))
}
}
fn get_request_body(
&self,

View File

@ -14,7 +14,9 @@ use hyperswitch_domain_models::{
CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData,
PaymentsPreProcessingData, PaymentsSyncData, ResponseId,
},
router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
router_response_types::{
MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
},
types::{
PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, RefundsRouterData,
@ -47,11 +49,58 @@ impl<T> From<(StringMinorUnit, T)> for NexixpayRouterData<T> {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexixpayRecurringAction {
NoRecurring,
SubsequentPayment,
ContractCreation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ContractType {
MitUnscheduled,
MitScheduled,
Cit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecurrenceRequest {
action: NexixpayRecurringAction,
contract_id: Secret<String>,
contract_type: ContractType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexixpayNonMandatePaymentRequest {
card: NexixpayCard,
recurrence: Option<RecurrenceRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexixpayMandatePaymentRequest {
contract_id: Secret<String>,
capture_type: Option<NexixpayCaptureType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum NexixpayPaymentsRequestData {
NexixpayNonMandatePaymentRequest(Box<NexixpayNonMandatePaymentRequest>),
NexixpayMandatePaymentRequest(Box<NexixpayMandatePaymentRequest>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexixpayPaymentsRequest {
order: Order,
card: NexixpayCard,
#[serde(flatten)]
payment_data: NexixpayPaymentsRequestData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -69,6 +118,7 @@ pub struct NexixpayCompleteAuthorizeRequest {
operation_id: String,
capture_type: Option<NexixpayCaptureType>,
three_d_s_auth_data: ThreeDSAuthData,
recurrence: Option<RecurrenceRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -108,13 +158,13 @@ pub struct Order {
#[serde(rename_all = "camelCase")]
pub struct CustomerInfo {
card_holder_name: Secret<String>,
billing_address: Address,
shipping_address: Option<Address>,
billing_address: BillingAddress,
shipping_address: Option<ShippingAddress>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Address {
pub struct BillingAddress {
name: Secret<String>,
street: Secret<String>,
city: String,
@ -122,6 +172,16 @@ pub struct Address {
country: enums::CountryAlpha2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShippingAddress {
name: Option<Secret<String>>,
street: Option<Secret<String>>,
city: Option<String>,
post_code: Option<Secret<String>>,
country: Option<enums::CountryAlpha2>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexixpayCard {
@ -137,12 +197,26 @@ struct Recurrence {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexixpayPaymentsResponse {
pub struct PaymentsResponse {
operation: Operation,
three_d_s_auth_request: String,
three_d_s_auth_url: Secret<url::Url>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NexixpayMandateResponse {
operation: Operation,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum NexixpayPaymentsResponse {
PaymentResponse(Box<PaymentsResponse>),
MandateResponse(Box<NexixpayMandateResponse>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreeDSAuthResult {
@ -371,23 +445,47 @@ impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaym
fn try_from(
item: &NexixpayRouterData<&PaymentsAuthorizeRouterData>,
) -> Result<Self, Self::Error> {
match item.router_data.request.payment_method_data {
PaymentMethodData::Card(ref req_card) => {
let card = NexixpayCard {
pan: req_card.card_number.clone(),
expiry_date: req_card.get_expiry_date_as_mmyy()?,
};
let billing_address = Address {
let billing_address_street = format!(
"{}, {}",
item.router_data.get_billing_line1()?.expose(),
item.router_data.get_billing_line2()?.expose()
);
let billing_address = BillingAddress {
name: item.router_data.get_billing_full_name()?,
street: item.router_data.get_billing_line1()?,
street: Secret::new(billing_address_street),
city: item.router_data.get_billing_city()?,
post_code: item.router_data.get_billing_zip()?,
country: item.router_data.get_billing_country()?,
};
let shipping_address_street = match (
item.router_data.get_optional_shipping_line1(),
item.router_data.get_optional_shipping_line2(),
) {
(Some(line1), Some(line2)) => Some(Secret::new(format!(
"{}, {}",
line1.expose(),
line2.expose()
))),
(Some(line1), None) => Some(Secret::new(line1.expose())),
(None, Some(line2)) => Some(Secret::new(line2.expose())),
(None, None) => None,
};
let shipping_address = item
.router_data
.get_optional_billing()
.map(|_| ShippingAddress {
name: item.router_data.get_optional_shipping_full_name(),
street: shipping_address_street,
city: item.router_data.get_optional_shipping_city(),
post_code: item.router_data.get_optional_shipping_zip(),
country: item.router_data.get_optional_shipping_country(),
});
let customer_info = CustomerInfo {
card_holder_name: item.router_data.get_billing_full_name()?,
billing_address: billing_address.clone(),
shipping_address: Some(billing_address),
shipping_address: shipping_address.clone(),
};
let order = Order {
order_id: item.router_data.connector_request_reference_id.clone(),
@ -396,7 +494,63 @@ impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaym
description: item.router_data.description.clone(),
customer_info,
};
Ok(Self { order, card })
let payment_data = NexixpayPaymentsRequestData::try_from(item)?;
Ok(Self {
order,
payment_data,
})
}
}
impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaymentsRequestData {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: &NexixpayRouterData<&PaymentsAuthorizeRouterData>,
) -> Result<Self, Self::Error> {
match item
.router_data
.request
.mandate_id
.clone()
.and_then(|mandate_id| mandate_id.mandate_reference_id)
{
None => {
let recurrence_request_obj = if item.router_data.request.is_mandate_payment() {
let contract_id = item
.router_data
.connector_mandate_request_reference_id
.clone()
.ok_or_else(|| errors::ConnectorError::MissingRequiredField {
field_name: "connector_mandate_request_reference_id",
})?;
Some(RecurrenceRequest {
action: NexixpayRecurringAction::ContractCreation,
contract_id: Secret::new(contract_id),
contract_type: ContractType::MitUnscheduled,
})
} else {
None
};
match item.router_data.request.payment_method_data {
PaymentMethodData::Card(ref req_card) => {
if item.router_data.is_three_ds() {
Ok(Self::NexixpayNonMandatePaymentRequest(Box::new(
NexixpayNonMandatePaymentRequest {
card: NexixpayCard {
pan: req_card.card_number.clone(),
expiry_date: req_card.get_expiry_date_as_mmyy()?,
},
recurrence: recurrence_request_obj,
},
)))
} else {
Err(errors::ConnectorError::NotSupported {
message: "No threeds is not supported".to_string(),
connector: "nexixpay",
}
.into())
}
}
PaymentMethodData::CardRedirect(_)
| PaymentMethodData::Wallet(_)
@ -408,20 +562,44 @@ impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaym
| PaymentMethodData::MandatePayment
| PaymentMethodData::Reward
| PaymentMethodData::RealTimePayment(_)
| PaymentMethodData::MobilePayment(_)
| PaymentMethodData::Upi(_)
| PaymentMethodData::MobilePayment(_)
| PaymentMethodData::Voucher(_)
| PaymentMethodData::GiftCard(_)
| PaymentMethodData::OpenBanking(_)
| PaymentMethodData::CardToken(_)
| PaymentMethodData::NetworkToken(_)
| PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
| PaymentMethodData::CardDetailsForNetworkTransactionId(_)
| PaymentMethodData::NetworkToken(_) => {
Err(errors::ConnectorError::NotImplemented(
get_unimplemented_payment_method_error_message("nexixpay"),
))?
}
}
}
Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => {
let contract_id = Secret::new(
mandate_data
.get_connector_mandate_request_reference_id()
.ok_or(errors::ConnectorError::MissingConnectorMandateID)?,
);
let capture_type =
get_nexixpay_capture_type(item.router_data.request.capture_method)?;
Ok(Self::NexixpayMandatePaymentRequest(Box::new(
NexixpayMandatePaymentRequest {
contract_id,
capture_type,
},
)))
}
Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_))
| Some(api_models::payments::MandateReferenceId::NetworkMandateId(_)) => {
Err(errors::ConnectorError::NotImplemented(
get_unimplemented_payment_method_error_message("nexixpay"),
)
.into())
}
}
}
}
pub struct NexixpayAuthType {
@ -598,11 +776,17 @@ impl<F>
PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
match item.response {
NexixpayPaymentsResponse::PaymentResponse(ref response_body) => {
let complete_authorize_url = item.data.request.get_complete_authorize_url()?;
let operation_id: String = item.response.operation.operation_id;
let operation_id: String = response_body.operation.operation_id.clone();
let redirection_form = nexixpay_threeds_link(NexixpayRedirectionRequest {
three_d_s_auth_url: item.response.three_d_s_auth_url.expose().to_string(),
three_ds_request: item.response.three_d_s_auth_request.clone(),
three_d_s_auth_url: response_body
.three_d_s_auth_url
.clone()
.expose()
.to_string(),
three_ds_request: response_body.three_d_s_auth_request.clone(),
return_url: complete_authorize_url.clone(),
transaction_id: operation_id.clone(),
})?;
@ -622,22 +806,52 @@ impl<F>
psync_flow: NexixpayPaymentIntent::Authorize
}));
Ok(Self {
status: AttemptStatus::from(item.response.operation.operation_result),
status: AttemptStatus::from(response_body.operation.operation_result.clone()),
response: Ok(PaymentsResponseData::TransactionResponse {
resource_id: ResponseId::ConnectorTransactionId(
item.response.operation.order_id.clone(),
response_body.operation.order_id.clone(),
),
redirection_data: Box::new(Some(redirection_form.clone())),
mandate_reference: Box::new(None),
mandate_reference: Box::new(Some(MandateReference {
connector_mandate_id: item
.data
.connector_mandate_request_reference_id
.clone(),
payment_method_id: None,
mandate_metadata: None,
connector_mandate_request_reference_id: None,
})),
connector_metadata,
network_txn_id: None,
connector_response_reference_id: Some(item.response.operation.order_id),
connector_response_reference_id: Some(
response_body.operation.order_id.clone(),
),
incremental_authorization_allowed: None,
charge_id: None,
}),
..item.data
})
}
NexixpayPaymentsResponse::MandateResponse(ref mandate_response) => Ok(Self {
status: AttemptStatus::from(mandate_response.operation.operation_result.clone()),
response: Ok(PaymentsResponseData::TransactionResponse {
resource_id: ResponseId::ConnectorTransactionId(
mandate_response.operation.order_id.clone(),
),
redirection_data: Box::new(None),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: Some(
mandate_response.operation.order_id.clone(),
),
incremental_authorization_allowed: None,
charge_id: None,
}),
..item.data
}),
}
}
}
fn nexixpay_threeds_link(
@ -738,7 +952,6 @@ impl<F>
meta_data,
is_auto_capture,
})?);
Ok(Self {
status: AttemptStatus::from(item.response.operation.operation_result),
response: Ok(PaymentsResponseData::TransactionResponse {
@ -746,7 +959,12 @@ impl<F>
item.response.operation.order_id.clone(),
),
redirection_data: Box::new(None),
mandate_reference: Box::new(None),
mandate_reference: Box::new(Some(MandateReference {
connector_mandate_id: item.data.connector_mandate_request_reference_id.clone(),
payment_method_id: None,
mandate_metadata: None,
connector_mandate_request_reference_id: None,
})),
connector_metadata,
network_txn_id: None,
connector_response_reference_id: Some(item.response.operation.order_id),
@ -775,17 +993,47 @@ impl TryFrom<&NexixpayRouterData<&PaymentsCompleteAuthorizeRouterData>>
let order_id = item.router_data.connector_request_reference_id.clone();
let amount = item.amount.clone();
let billing_address = Address {
let billing_address_street = format!(
"{}, {}",
item.router_data.get_billing_line1()?.expose(),
item.router_data.get_billing_line2()?.expose()
);
let billing_address = BillingAddress {
name: item.router_data.get_billing_full_name()?,
street: item.router_data.get_billing_line1()?,
street: Secret::new(billing_address_street),
city: item.router_data.get_billing_city()?,
post_code: item.router_data.get_billing_zip()?,
country: item.router_data.get_billing_country()?,
};
let shipping_address_street = match (
item.router_data.get_optional_shipping_line1(),
item.router_data.get_optional_shipping_line2(),
) {
(Some(line1), Some(line2)) => Some(Secret::new(format!(
"{}, {}",
line1.expose(),
line2.expose()
))),
(Some(line1), None) => Some(Secret::new(line1.expose())),
(None, Some(line2)) => Some(Secret::new(line2.expose())),
(None, None) => None,
};
let shipping_address = item
.router_data
.get_optional_billing()
.map(|_| ShippingAddress {
name: item.router_data.get_optional_shipping_full_name(),
street: shipping_address_street,
city: item.router_data.get_optional_shipping_city(),
post_code: item.router_data.get_optional_shipping_zip(),
country: item.router_data.get_optional_shipping_country(),
});
let customer_info = CustomerInfo {
card_holder_name: item.router_data.get_billing_full_name()?,
billing_address: billing_address.clone(),
shipping_address: Some(billing_address),
shipping_address: shipping_address.clone(),
};
let order_data = Order {
order_id,
@ -841,12 +1089,25 @@ impl TryFrom<&NexixpayRouterData<&PaymentsCompleteAuthorizeRouterData>>
.into())
}
};
let contract_id = Secret::new(
item.router_data
.connector_mandate_request_reference_id
.clone()
.ok_or_else(|| errors::ConnectorError::MissingRequiredField {
field_name: "connector_mandate_request_reference_id",
})?,
);
Ok(Self {
order: order_data,
card: card?,
operation_id,
capture_type,
three_d_s_auth_data,
recurrence: Some(RecurrenceRequest {
action: NexixpayRecurringAction::ContractCreation,
contract_id,
contract_type: ContractType::MitUnscheduled,
}),
})
}
}
@ -870,7 +1131,12 @@ impl<F>
response: Ok(PaymentsResponseData::TransactionResponse {
resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()),
redirection_data: Box::new(None),
mandate_reference: Box::new(None),
mandate_reference: Box::new(Some(MandateReference {
connector_mandate_id: item.data.connector_mandate_request_reference_id.clone(),
payment_method_id: None,
mandate_metadata: None,
connector_mandate_request_reference_id: None,
})),
connector_metadata: item.data.request.connector_meta.clone(),
network_txn_id: None,
connector_response_reference_id: Some(item.response.order_id.clone()),

View File

@ -300,6 +300,7 @@ pub trait RouterData {
fn get_optional_shipping_state(&self) -> Option<Secret<String>>;
fn get_optional_shipping_first_name(&self) -> Option<Secret<String>>;
fn get_optional_shipping_last_name(&self) -> Option<Secret<String>>;
fn get_optional_shipping_full_name(&self) -> Option<Secret<String>>;
fn get_optional_shipping_phone_number(&self) -> Option<Secret<String>>;
fn get_optional_shipping_email(&self) -> Option<Email>;
@ -369,6 +370,12 @@ impl<Flow, Request, Response> RouterData
})
}
fn get_optional_shipping_full_name(&self) -> Option<Secret<String>> {
self.get_optional_shipping()
.and_then(|shipping_details| shipping_details.address.as_ref())
.and_then(|shipping_address| shipping_address.get_optional_full_name())
}
fn get_optional_shipping_line1(&self) -> Option<Secret<String>> {
self.address.get_shipping().and_then(|shipping_address| {
shipping_address
@ -1177,6 +1184,7 @@ pub trait PaymentsAuthorizeRequestData {
fn get_card_holder_name_from_additional_payment_method_data(
&self,
) -> Result<Secret<String>, Error>;
fn get_connector_mandate_request_reference_id(&self) -> Result<String, Error>;
}
impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData {
@ -1355,6 +1363,20 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData {
.into()),
}
}
/// Attempts to retrieve the connector mandate reference ID as a `Result<String, Error>`.
fn get_connector_mandate_request_reference_id(&self) -> Result<String, Error> {
self.mandate_id
.as_ref()
.and_then(|mandate_ids| match &mandate_ids.mandate_reference_id {
Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => {
connector_mandate_ids.get_connector_mandate_request_reference_id()
}
Some(payments::MandateReferenceId::NetworkMandateId(_))
| None
| Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None,
})
.ok_or_else(missing_field_err("connector_mandate_request_reference_id"))
}
}
pub trait PaymentsCaptureRequestData {
@ -1514,6 +1536,7 @@ pub trait PaymentsCompleteAuthorizeRequestData {
fn get_redirect_response_payload(&self) -> Result<pii::SecretSerdeValue, Error>;
fn get_complete_authorize_url(&self) -> Result<String, Error>;
fn is_mandate_payment(&self) -> bool;
fn get_connector_mandate_request_reference_id(&self) -> Result<String, Error>;
}
impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData {
@ -1554,6 +1577,20 @@ impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData {
.and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref())
.is_some()
}
/// Attempts to retrieve the connector mandate reference ID as a `Result<String, Error>`.
fn get_connector_mandate_request_reference_id(&self) -> Result<String, Error> {
self.mandate_id
.as_ref()
.and_then(|mandate_ids| match &mandate_ids.mandate_reference_id {
Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => {
connector_mandate_ids.get_connector_mandate_request_reference_id()
}
Some(payments::MandateReferenceId::NetworkMandateId(_))
| None
| Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None,
})
.ok_or_else(missing_field_err("connector_mandate_request_reference_id"))
}
}
pub trait AddressData {
fn get_optional_full_name(&self) -> Option<Secret<String>>;

View File

@ -1952,6 +1952,98 @@ impl Default for settings::RequiredFields {
),
}
),
(
enums::Connector::Nexixpay,
RequiredFieldFinal {
mandate: HashMap::new(),
non_mandate: HashMap::new(),
common: HashMap::from(
[
(
"payment_method_data.card.card_number".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.card.card_number".to_string(),
display_name: "card_number".to_string(),
field_type: enums::FieldType::UserCardNumber,
value: None,
}
),
(
"payment_method_data.card.card_exp_month".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.card.card_exp_month".to_string(),
display_name: "card_exp_month".to_string(),
field_type: enums::FieldType::UserCardExpiryMonth,
value: None,
}
),
(
"payment_method_data.card.card_exp_year".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.card.card_exp_year".to_string(),
display_name: "card_exp_year".to_string(),
field_type: enums::FieldType::UserCardExpiryYear,
value: None,
}
),
(
"billing.address.line1".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.billing.address.line1".to_string(),
display_name: "line1".to_string(),
field_type: enums::FieldType::UserAddressLine1,
value: None,
}
),
(
"billing.address.line2".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.billing.address.line2".to_string(),
display_name: "line1".to_string(),
field_type: enums::FieldType::UserAddressLine2,
value: None,
}
),
(
"billing.address.city".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.billing.address.city".to_string(),
display_name: "city".to_string(),
field_type: enums::FieldType::UserAddressCity,
value: None,
}
),
(
"billing.address.zip".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.billing.address.zip".to_string(),
display_name: "zip".to_string(),
field_type: enums::FieldType::UserAddressPincode,
value: None,
}
),
(
"billing.address.first_name".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.billing.address.first_name".to_string(),
display_name: "first_name".to_string(),
field_type: enums::FieldType::UserFullName,
value: None,
}
),
(
"billing.address.last_name".to_string(),
RequiredFieldInfo {
required_field: "payment_method_data.billing.address.last_name".to_string(),
display_name: "last_name".to_string(),
field_type: enums::FieldType::UserFullName,
value: None,
}
)
]
),
}
),
(
enums::Connector::Nmi,
RequiredFieldFinal {

View File

@ -1,19 +1,84 @@
const successfulNo3DSCardDetails = {
card_number: "4111111111111111",
card_exp_month: "08",
card_exp_year: "35",
card_holder_name: "joseph Doe",
card_cvc: "999",
};
const successfulThreeDSTestCardDetails = {
card_number: "4349940199004549",
card_exp_month: "12",
card_exp_year: "30",
card_exp_year: "35",
card_holder_name: "joseph Doe",
card_cvc: "396",
};
const customerAcceptance = {
acceptance_type: "offline",
accepted_at: "1963-05-03T04:07:52.723Z",
online: {
ip_address: "125.0.0.1",
user_agent: "amet irure esse",
},
};
const multiUseMandateData = {
customer_acceptance: customerAcceptance,
mandate_type: {
multi_use: {
amount: 8000,
currency: "EUR",
},
},
};
const singleUseMandateData = {
customer_acceptance: customerAcceptance,
mandate_type: {
multi_use: {
amount: 8000,
currency: "EUR",
},
},
};
const billingAddress = {
address: {
line1: "1467",
line2: "Harrison Street",
line3: "Harrison Street",
city: "San Fransico",
state: "California",
zip: "94122",
country: "IT",
first_name: "joseph",
last_name: "Doe",
},
email: "mauro.morandi@nexi.it",
phone: {
number: "9123456789",
country_code: "+91",
},
};
const no3DSNotSupportedResponseBody = {
error: {
type: "invalid_request",
message: "No threeds is not supported",
code: "IR_00",
},
};
export const connectorDetails = {
card_pm: {
PaymentIntent: {
Request: {
currency: "EUR",
amount: 3545,
amount: 6500,
customer_acceptance: null,
setup_future_usage: "on_session",
billing: billingAddress,
},
Response: {
status: 200,
@ -22,61 +87,29 @@ export const connectorDetails = {
},
},
},
"3DSManualCapture": {
PaymentIntentOffSession: {
Request: {
payment_method: "card",
billing: {
address: {
line1: "1467",
line2: "CA",
line3: "CA",
city: "Florence",
state: "Tuscany",
zip: "12345",
country: "IT",
first_name: "Max",
last_name: "Mustermann",
},
email: "mauro.morandi@nexi.it",
phone: {
number: "9123456789",
country_code: "+91",
},
},
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
amount: 6500,
authentication_type: "no_three_ds",
customer_acceptance: null,
setup_future_usage: "on_session",
setup_future_usage: "off_session",
},
Response: {
status: 200,
body: {
status: "requires_capture",
status: "requires_payment_method",
setup_future_usage: "off_session",
},
},
},
"3DSAutoCapture": {
"3DSManualCapture": {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
billing: {
address: {
line1: "1467",
line2: "CA",
line3: "CA",
city: "Florence",
state: "Tuscany",
zip: "12345",
country: "IT",
first_name: "Max",
last_name: "Mustermann",
},
email: "mauro.morandi@nexi.it",
phone: {
number: "9123456789",
country_code: "+91",
},
},
billing: billingAddress,
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
@ -90,7 +123,62 @@ export const connectorDetails = {
},
},
},
"3DSAutoCapture": {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
billing: billingAddress,
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
customer_acceptance: null,
setup_future_usage: "on_session",
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
No3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
billing: billingAddress,
},
Response: {
status: 400,
body: no3DSNotSupportedResponseBody,
},
},
No3DSAutoCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
customer_acceptance: null,
setup_future_usage: "on_session",
billing: billingAddress,
},
Response: {
status: 400,
body: no3DSNotSupportedResponseBody,
},
},
Capture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
@ -102,20 +190,23 @@ export const connectorDetails = {
status: 200,
body: {
status: "processing",
amount: 3545,
amount_capturable: 0,
amount_received: 3545,
amount: 6500,
amount_capturable: 6500,
amount_received: null,
},
},
},
PartialCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {},
Response: {
status: 200,
body: {
status: "processing",
amount: 3545,
amount_capturable: 0,
amount: 6500,
amount_capturable: 6500,
amount_received: 100,
},
},
@ -130,6 +221,9 @@ export const connectorDetails = {
},
},
Refund: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
@ -140,11 +234,14 @@ export const connectorDetails = {
Response: {
status: 200,
body: {
status: "processing",
status: "pending",
},
},
},
PartialRefund: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
@ -155,7 +252,7 @@ export const connectorDetails = {
Response: {
status: 200,
body: {
status: "processing",
status: "pending",
},
},
},
@ -174,5 +271,329 @@ export const connectorDetails = {
},
},
},
MandateMultiUse3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: multiUseMandateData,
billing: billingAddress,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
MandateMultiUse3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: multiUseMandateData,
billing: billingAddress,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
MandateMultiUseNo3DSAutoCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: multiUseMandateData,
billing: billingAddress,
},
Response: {
status: 400,
body: no3DSNotSupportedResponseBody,
},
},
MandateMultiUseNo3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: multiUseMandateData,
billing: billingAddress,
},
Response: {
status: 400,
body: no3DSNotSupportedResponseBody,
},
},
MandateSingleUse3DSAutoCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
billing: billingAddress,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
MandateSingleUse3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
billing: billingAddress,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
MandateSingleUseNo3DSAutoCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
billing: billingAddress,
},
Response: {
status: 400,
body: no3DSNotSupportedResponseBody,
},
},
MandateSingleUseNo3DSManualCapture: {
Request: {
payment_method: "card",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
billing: billingAddress,
},
Response: {
status: 400,
body: no3DSNotSupportedResponseBody,
},
},
manualPaymentRefund: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
customer_acceptance: null,
},
Response: {
status: 200,
body: {
status: "pending",
},
},
},
ZeroAuthMandate: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
mandate_data: singleUseMandateData,
},
Response: {
status: 200,
body: {
status: "processing",
},
},
},
PaymentMethodIdMandateNo3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
amount: 6500,
mandate_data: null,
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
PaymentMethodIdMandateNo3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
amount: 6500,
mandate_data: null,
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
PaymentMethodIdMandate3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
amount: 6500,
mandate_data: null,
authentication_type: "three_ds",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
PaymentMethodIdMandate3DSManualCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_data: {
card: successfulThreeDSTestCardDetails,
},
currency: "EUR",
amount: 6500,
mandate_data: null,
authentication_type: "three_ds",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
SaveCardUseNo3DSAutoCapture: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
payment_method_type: "debit",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
currency: "EUR",
billing: billingAddress,
setup_future_usage: "on_session",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
SaveCardUseNo3DSAutoCaptureOffSession: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
currency: "EUR",
billing: billingAddress,
payment_method_type: "debit",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
setup_future_usage: "off_session",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
SaveCardUseNo3DSManualCaptureOffSession: {
Configs: {
TRIGGER_SKIP: true,
},
Request: {
payment_method: "card",
currency: "EUR",
billing: billingAddress,
payment_method_type: "debit",
payment_method_data: {
card: successfulNo3DSCardDetails,
},
setup_future_usage: "off_session",
customer_acceptance: customerAcceptance,
},
Response: {
status: 200,
body: {
status: "requires_customer_action",
},
},
},
},
};