diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index d9dace6d19..84cfe8e619 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -10987,6 +10987,7 @@ "nuvei", "opennode", "paybox", + "payload", "payme", "payone", "paypal", @@ -27990,6 +27991,7 @@ "opennode", "paybox", "payme", + "payload", "payone", "paypal", "paystack", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 76a35e926e..63a25bf4fc 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -8194,6 +8194,7 @@ "nuvei", "opennode", "paybox", + "payload", "payme", "payone", "paypal", @@ -22707,6 +22708,7 @@ "opennode", "paybox", "payme", + "payload", "payone", "paypal", "paystack", diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index fd145d6148..e975dc8bcb 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -121,7 +121,7 @@ pub enum RoutableConnectors { // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage Paybox, Payme, - // Payload, + Payload, Payone, Paypal, Paystack, @@ -284,7 +284,7 @@ pub enum Connector { Opennode, Paybox, // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage - // Payload, + Payload, Payme, Payone, Paypal, @@ -459,7 +459,7 @@ impl Connector { | Self::Nuvei | Self::Opennode | Self::Paybox - // | Self::Payload + | Self::Payload | Self::Payme | Self::Payone | Self::Paypal @@ -621,7 +621,7 @@ impl From for Connector { RoutableConnectors::Nuvei => Self::Nuvei, RoutableConnectors::Opennode => Self::Opennode, RoutableConnectors::Paybox => Self::Paybox, - // RoutableConnectors::Paybox => Self::Payload, + RoutableConnectors::Payload => Self::Payload, RoutableConnectors::Payme => Self::Payme, RoutableConnectors::Payone => Self::Payone, RoutableConnectors::Paypal => Self::Paypal, @@ -742,7 +742,7 @@ impl TryFrom for RoutableConnectors { Connector::Nuvei => Ok(Self::Nuvei), Connector::Opennode => Ok(Self::Opennode), Connector::Paybox => Ok(Self::Paybox), - // Connector::Paybox => Ok(Self::Payload), + Connector::Payload => Ok(Self::Payload), Connector::Payme => Ok(Self::Payme), Connector::Payone => Ok(Self::Payone), Connector::Paypal => Ok(Self::Paypal), diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index f82ceecb05..6ec11e8ca7 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -430,7 +430,7 @@ impl ConnectorConfig { Connector::Noon => Ok(connector_data.noon), Connector::Nuvei => Ok(connector_data.nuvei), Connector::Paybox => Ok(connector_data.paybox), - // Connector::Payload => Ok(connector_data.payload), + Connector::Payload => Ok(connector_data.payload), Connector::Payme => Ok(connector_data.payme), Connector::Payone => Err("Use get_payout_connector_config".to_string()), Connector::Paypal => Ok(connector_data.paypal), diff --git a/crates/hyperswitch_connectors/src/connectors/payload.rs b/crates/hyperswitch_connectors/src/connectors/payload.rs index b4d930bf03..c1f1516e63 100644 --- a/crates/hyperswitch_connectors/src/connectors/payload.rs +++ b/crates/hyperswitch_connectors/src/connectors/payload.rs @@ -1,13 +1,17 @@ +mod requests; +mod responses; pub mod transformers; use std::sync::LazyLock; +use base64::Engine; use common_enums::enums; use common_utils::{ + consts::BASE64_ENGINE, errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -24,11 +28,12 @@ use hyperswitch_domain_models::{ RefundsData, SetupMandateRequestData, }, router_response_types::{ - ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -39,7 +44,7 @@ use hyperswitch_interfaces::{ configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, PaymentsVoidType, Response}, webhooks, }; use masking::{ExposeInterface, Mask}; @@ -49,13 +54,13 @@ use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] pub struct Payload { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Payload { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } } @@ -90,7 +95,7 @@ where ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), + Self::common_get_content_type(self).to_string().into(), )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); @@ -104,11 +109,11 @@ impl ConnectorCommon for Payload { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base + api::CurrencyUnit::Minor } fn common_get_content_type(&self) -> &'static str { - "application/json" + "application/x-www-form-urlencoded" } fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { @@ -121,9 +126,10 @@ impl ConnectorCommon for Payload { ) -> CustomResult)>, errors::ConnectorError> { let auth = payload::PayloadAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.expose())); Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + format!("Basic {encoded_api_key}").into_masked(), )]) } @@ -132,7 +138,7 @@ impl ConnectorCommon for Payload { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: payload::PayloadErrorResponse = res + let response: responses::PayloadErrorResponse = res .response .parse_struct("PayloadErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -142,9 +148,12 @@ impl ConnectorCommon for Payload { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.error_type, + message: response.error_description, + reason: response + .details + .as_ref() + .map(|details_value| details_value.to_string()), attempt_status: None, connector_transaction_id: None, network_advice_code: None, @@ -204,9 +213,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/transactions", self.base_url(connectors))) } fn get_request_body( @@ -221,8 +230,9 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: payload::PayloadPaymentsResponse = res + let response: responses::PayloadPaymentsResponse = res .response - .parse_struct("Payload PaymentsAuthorizeResponse") + .parse_struct("PayloadPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { response, data: data.clone(), @@ -290,10 +302,20 @@ impl ConnectorIntegration for Pay fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + + Ok(format!( + "{}/transactions/{}", + self.base_url(connectors), + payment_id + )) } fn build_request( @@ -317,9 +339,9 @@ impl ConnectorIntegration for Pay event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: payload::PayloadPaymentsResponse = res + let response: responses::PayloadPaymentsResponse = res .response - .parse_struct("payload PaymentsSyncResponse") + .parse_struct("PayloadPaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -354,18 +376,32 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_transaction_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/transactions/{}", + self.base_url(connectors), + connector_transaction_id + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = payload::PayloadRouterData::from((amount, req)); + let connector_req = requests::PayloadCaptureRequest::try_from(&connector_router_data)?; + + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -375,7 +411,7 @@ impl ConnectorIntegration fo ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Post) + .method(Method::Put) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -394,9 +430,9 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: payload::PayloadPaymentsResponse = res + let response: responses::PayloadPaymentsResponse = res .response - .parse_struct("Payload PaymentsCaptureResponse") + .parse_struct("PayloadPaymentsCaptureResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -416,7 +452,90 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Payload {} +impl ConnectorIntegration for Payload { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let payment_id = &req.request.connector_transaction_id; + Ok(format!( + "{}/transactions/{}", + self.base_url(connectors), + payment_id + )) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_router_data = payload::PayloadRouterData::from((Default::default(), req)); + let connector_req = requests::PayloadCancelRequest::try_from(&connector_router_data)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Put) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult< + RouterData, + errors::ConnectorError, + > { + let response: responses::PayloadPaymentsResponse = res + .response + .parse_struct("PayloadPaymentsCancelResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} impl ConnectorIntegration for Payload { fn get_headers( @@ -434,9 +553,9 @@ impl ConnectorIntegration for Payload fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/transactions", self.base_url(connectors))) } fn get_request_body( @@ -451,8 +570,8 @@ impl ConnectorIntegration for Payload )?; let connector_router_data = payload::PayloadRouterData::from((refund_amount, req)); - let connector_req = payload::PayloadRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) + let connector_req = requests::PayloadRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -480,12 +599,14 @@ impl ConnectorIntegration for Payload event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: payload::RefundResponse = res + let response: responses::PayloadRefundResponse = res .response - .parse_struct("payload RefundResponse") + .parse_struct("PayloadRefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { response, data: data.clone(), @@ -517,10 +638,19 @@ impl ConnectorIntegration for Payload { fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_refund_id = req + .request + .connector_refund_id + .as_ref() + .ok_or_else(|| errors::ConnectorError::MissingConnectorRefundID)?; + Ok(format!( + "{}/transactions/{}", + self.base_url(connectors), + connector_refund_id + )) } fn build_request( @@ -547,9 +677,9 @@ impl ConnectorIntegration for Payload { event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: payload::RefundResponse = res + let response: responses::PayloadRefundResponse = res .response - .parse_struct("payload RefundSyncResponse") + .parse_struct("PayloadRefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -593,12 +723,62 @@ impl webhooks::IncomingWebhook for Payload { } } -static PAYLOAD_SUPPORTED_PAYMENT_METHODS: LazyLock = - LazyLock::new(SupportedPaymentMethods::new); +static PAYLOAD_SUPPORTED_PAYMENT_METHODS: LazyLock = LazyLock::new(|| { + let mut payload_supported_payment_methods = SupportedPaymentMethods::new(); + let supported_capture_methods = vec![ + enums::CaptureMethod::Automatic, + enums::CaptureMethod::Manual, + enums::CaptureMethod::SequentialAutomatic, + ]; + let supported_card_network = vec![ + common_enums::CardNetwork::AmericanExpress, + common_enums::CardNetwork::Discover, + common_enums::CardNetwork::Mastercard, + common_enums::CardNetwork::Visa, + ]; + + payload_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Credit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + }, + ); + payload_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + }, + ); + payload_supported_payment_methods +}); static PAYLOAD_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { display_name: "Payload", - description: "Payload connector", + description: "Payload is an embedded finance solution for modern platforms and businesses, automating inbound and outbound payments with an industry-leading platform and driving innovation into the future.", connector_type: enums::PaymentConnectorCategory::PaymentGateway, }; diff --git a/crates/hyperswitch_connectors/src/connectors/payload/requests.rs b/crates/hyperswitch_connectors/src/connectors/payload/requests.rs new file mode 100644 index 0000000000..6f1bc122af --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/payload/requests.rs @@ -0,0 +1,85 @@ +use common_utils::types::StringMajorUnit; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::connectors::payload::responses; + +#[derive(Debug, Serialize, PartialEq)] +#[serde(untagged)] +pub enum PayloadPaymentsRequest { + PayloadCardsRequest(PayloadCardsRequestData), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum TransactionTypes { + Credit, + Chargeback, + ChargebackReversal, + Deposit, + Payment, + Refund, + Reversal, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BillingAddress { + #[serde(rename = "payment_method[billing_address][city]")] + pub city: String, + #[serde(rename = "payment_method[billing_address][country_code]")] + pub country: common_enums::CountryAlpha2, + #[serde(rename = "payment_method[billing_address][postal_code]")] + pub postal_code: Secret, + #[serde(rename = "payment_method[billing_address][state_province]")] + pub state_province: Secret, + #[serde(rename = "payment_method[billing_address][street_address]")] + pub street_address: Secret, +} + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub struct PayloadCardsRequestData { + pub amount: StringMajorUnit, + #[serde(flatten)] + pub card: PayloadCard, + #[serde(rename = "type")] + pub transaction_types: TransactionTypes, + // For manual capture, set status to "authorized", otherwise omit + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(rename = "payment_method[type]")] + pub payment_method_type: String, + // Billing address fields are for AVS validation + #[serde(flatten)] + pub billing_address: BillingAddress, +} + +#[derive(Default, Clone, Debug, Serialize, Eq, PartialEq)] +pub struct PayloadCard { + #[serde(rename = "payment_method[card][card_number]")] + pub number: cards::CardNumber, + #[serde(rename = "payment_method[card][expiry]")] + pub expiry: Secret, + #[serde(rename = "payment_method[card][card_code]")] + pub cvc: Secret, +} + +#[derive(Clone, Debug, Serialize, PartialEq)] +pub struct PayloadCancelRequest { + pub status: responses::PayloadPaymentStatus, +} + +// Type definition for CaptureRequest +#[derive(Clone, Debug, Serialize, PartialEq)] +pub struct PayloadCaptureRequest { + pub status: responses::PayloadPaymentStatus, +} + +// Type definition for RefundRequest +#[derive(Debug, Serialize)] +pub struct PayloadRefundRequest { + #[serde(rename = "type")] + pub transaction_type: TransactionTypes, + pub amount: StringMajorUnit, + #[serde(rename = "ledger[0][assoc_transaction_id]")] + pub ledger_assoc_transaction_id: String, +} diff --git a/crates/hyperswitch_connectors/src/connectors/payload/responses.rs b/crates/hyperswitch_connectors/src/connectors/payload/responses.rs new file mode 100644 index 0000000000..63699d0981 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/payload/responses.rs @@ -0,0 +1,101 @@ +use masking::Secret; +use serde::{Deserialize, Serialize}; + +// PaymentsResponse +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum PayloadPaymentStatus { + Authorized, + Declined, + Processed, + #[default] + Processing, + Rejected, + Voided, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PayloadPaymentsResponse { + PayloadCardsResponse(PayloadCardsResponseData), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum AvsResponse { + Unknown, + NoMatch, + Zip, + Street, + StreetAndZip, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PayloadCardsResponseData { + pub amount: f64, + pub avs: Option, + pub customer_id: Option, + #[serde(rename = "id")] + pub transaction_id: String, + pub payment_method_id: Option>, + pub processing_id: Option, + pub processing_method_id: Option, + pub ref_number: Option, + pub status: PayloadPaymentStatus, + pub status_code: Option, + pub status_message: Option, + #[serde(rename = "type")] + pub response_type: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PayloadCardResponse { + pub card_brand: String, + pub card_number: String, // Masked card number like "xxxxxxxxxxxx4242" + pub card_type: String, + pub expiry: Secret, +} + +// Type definition for Refund Response +// Added based on assumptions since this is not provided in the documentation +#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum RefundStatus { + Declined, + Processed, + #[default] + Processing, + Rejected, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundsLedger { + pub amount: f64, + #[serde(rename = "assoc_transaction_id")] + pub associated_transaction_id: String, // Connector transaction id + #[serde(rename = "id")] + pub ledger_id: Secret, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct PayloadRefundResponse { + pub amount: f64, + #[serde(rename = "id")] + pub transaction_id: String, + pub ledger: Vec, + pub payment_method_id: Option>, + pub processing_id: Option, + pub ref_number: Option, + pub status: RefundStatus, + pub status_code: Option, + pub status_message: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct PayloadErrorResponse { + pub error_type: String, + pub error_description: String, + pub object: String, + /// Payload returns arbitrary details in JSON format + pub details: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs b/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs index 9275cd1a15..994b4a7cfc 100644 --- a/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs @@ -1,31 +1,30 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::types::StringMajorUnit; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + types::{PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, RefundsRouterData}, }; use hyperswitch_interfaces::errors; use masking::Secret; -use serde::{Deserialize, Serialize}; +use super::{requests, responses}; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::{is_manual_capture, CardData, RouterData as OtherRouterData}, }; //TODO: Fill the struct with respective fields pub struct PayloadRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } -impl From<(StringMinorUnit, T)> for PayloadRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(StringMajorUnit, T)> for PayloadRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,47 +32,61 @@ impl From<(StringMinorUnit, T)> for PayloadRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct PayloadPaymentsRequest { - amount: StringMinorUnit, - card: PayloadCard, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct PayloadCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, -} - -impl TryFrom<&PayloadRouterData<&PaymentsAuthorizeRouterData>> for PayloadPaymentsRequest { +impl TryFrom<&PayloadRouterData<&PaymentsAuthorizeRouterData>> + for requests::PayloadPaymentsRequest +{ type Error = error_stack::Report; fn try_from( item: &PayloadRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + if item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Cards 3DS".to_string(), + connector: "Payload", + })? + } + match item.router_data.request.payment_method_data.clone() { PaymentMethodData::Card(req_card) => { - let card = PayloadCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, + let card = requests::PayloadCard { + number: req_card.clone().card_number, + expiry: req_card + .clone() + .get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned())?, cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, }; - Ok(Self { - amount: item.amount.clone(), - card, - }) + let address = item.router_data.get_billing_address()?; + let billing_address = requests::BillingAddress { + city: address.city.clone().unwrap_or_default(), + country: address.country.unwrap_or_default(), + postal_code: address.zip.clone().unwrap_or_default(), + state_province: address.state.clone().unwrap_or_default(), + street_address: address.line1.clone().unwrap_or_default(), + }; + + // For manual capture, set status to "authorized" + let status = if is_manual_capture(item.router_data.request.capture_method) { + Some(responses::PayloadPaymentStatus::Authorized) + } else { + None + }; + + Ok(Self::PayloadCardsRequest( + requests::PayloadCardsRequestData { + amount: item.amount.clone(), + card, + transaction_types: requests::TransactionTypes::Payment, + payment_method_type: "card".to_string(), + status, + billing_address, + }, + )) } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } -//TODO: Fill the struct with respective fields // Auth Struct pub struct PayloadAuthType { pub(super) api_key: Secret, @@ -90,112 +103,105 @@ impl TryFrom<&ConnectorAuthType> for PayloadAuthType { } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum PayloadPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, -} -impl From for common_enums::AttemptStatus { - fn from(item: PayloadPaymentStatus) -> Self { +impl From for common_enums::AttemptStatus { + fn from(item: responses::PayloadPaymentStatus) -> Self { match item { - PayloadPaymentStatus::Succeeded => Self::Charged, - PayloadPaymentStatus::Failed => Self::Failure, - PayloadPaymentStatus::Processing => Self::Authorizing, + responses::PayloadPaymentStatus::Authorized => Self::Authorized, + responses::PayloadPaymentStatus::Processed => Self::Charged, + responses::PayloadPaymentStatus::Processing => Self::Pending, + responses::PayloadPaymentStatus::Rejected + | responses::PayloadPaymentStatus::Declined => Self::Failure, + responses::PayloadPaymentStatus::Voided => Self::Voided, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct PayloadPaymentsResponse { - status: PayloadPaymentStatus, - id: String, -} - -impl TryFrom> +impl + TryFrom> for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, ) -> Result { - Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charges: None, - }), - ..item.data - }) - } -} + match item.response.clone() { + responses::PayloadPaymentsResponse::PayloadCardsResponse(response) => { + let payment_status = response.status; + let transaction_id = response.transaction_id; -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] -pub struct PayloadRefundRequest { - pub amount: StringMinorUnit, -} - -impl TryFrom<&PayloadRouterData<&RefundsRouterData>> for PayloadRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &PayloadRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - amount: item.amount.to_owned(), - }) - } -} - -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + Ok(Self { + status: common_enums::AttemptStatus::from(payment_status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: response.ref_number, + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, +impl TryFrom<&PayloadRouterData> for requests::PayloadCancelRequest { + type Error = error_stack::Report; + fn try_from(_item: &PayloadRouterData) -> Result { + Ok(Self { + status: responses::PayloadPaymentStatus::Voided, + }) + } } -impl TryFrom> for RefundsRouterData { +impl TryFrom<&PayloadRouterData<&PaymentsCaptureRouterData>> for requests::PayloadCaptureRequest { type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + _item: &PayloadRouterData<&PaymentsCaptureRouterData>, + ) -> Result { + Ok(Self { + status: responses::PayloadPaymentStatus::Processed, + }) + } +} + +impl TryFrom<&PayloadRouterData<&RefundsRouterData>> for requests::PayloadRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &PayloadRouterData<&RefundsRouterData>) -> Result { + let connector_transaction_id = item.router_data.request.connector_transaction_id.clone(); + + Ok(Self { + transaction_type: requests::TransactionTypes::Refund, + amount: item.amount.to_owned(), + ledger_assoc_transaction_id: connector_transaction_id, + }) + } +} + +impl From for enums::RefundStatus { + fn from(item: responses::RefundStatus) -> Self { + match item { + responses::RefundStatus::Processed => Self::Success, + responses::RefundStatus::Processing => Self::Pending, + responses::RefundStatus::Declined | responses::RefundStatus::Rejected => Self::Failure, + } + } +} + +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), + connector_refund_id: item.response.transaction_id.to_string(), refund_status: enums::RefundStatus::from(item.response.status), }), ..item.data @@ -203,29 +209,19 @@ impl TryFrom> for RefundsRout } } -impl TryFrom> for RefundsRouterData { +impl TryFrom> + for RefundsRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), + connector_refund_id: item.response.transaction_id.to_string(), refund_status: enums::RefundStatus::from(item.response.status), }), ..item.data }) } } - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct PayloadErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, - pub network_advice_code: Option, - pub network_decline_code: Option, - pub network_error_message: Option, -} diff --git a/crates/payment_methods/src/configs/payment_connector_required_fields.rs b/crates/payment_methods/src/configs/payment_connector_required_fields.rs index 188b55ca53..08baca7e35 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -1431,6 +1431,25 @@ fn get_cards_required_fields() -> HashMap { .concat(), ), ), + ( + Connector::Payload, + fields( + vec![], + vec![], + [ + email(), + card_with_name(), + vec![ + RequiredField::BillingAddressLine1, + RequiredField::BillingAddressCity, + RequiredField::BillingAddressZip, + RequiredField::BillingAddressState, + RequiredField::BillingAddressCountries(vec!["ALL"]), + ], + ] + .concat(), + ), + ), ( Connector::Payme, fields(vec![], vec![], [email(), card_with_name()].concat()), diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index 587c817e7d..d23a3a175f 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -345,6 +345,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { paybox::transformers::PayboxAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Payload => { + payload::transformers::PayloadAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Payme => { payme::transformers::PaymeAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index e48c0268e8..3fa8aebf2c 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -529,9 +529,9 @@ impl ConnectorData { Ok(ConnectorEnum::Old(Box::new(connector::Paybox::new()))) } // "payeezy" => Ok(ConnectorIntegrationEnum::Old(Box::new(&connector::Payeezy)), As psync and rsync are not supported by this connector, it is added as template code for future usage - // enums::Connector::Payload => { - // Ok(ConnectorEnum::Old(Box::new(connector::Paybload::new()))) - // } + enums::Connector::Payload => { + Ok(ConnectorEnum::Old(Box::new(connector::Payload::new()))) + } enums::Connector::Payme => { Ok(ConnectorEnum::Old(Box::new(connector::Payme::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 895e1764ad..284a5243d0 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -297,7 +297,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Nuvei => Self::Nuvei, api_enums::Connector::Opennode => Self::Opennode, api_enums::Connector::Paybox => Self::Paybox, - // api_enums::Connector::Payload => Self::Payload, + api_enums::Connector::Payload => Self::Payload, api_enums::Connector::Payme => Self::Payme, api_enums::Connector::Payone => Self::Payone, api_enums::Connector::Paypal => Self::Paypal, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Payload.js b/cypress-tests/cypress/e2e/configs/Payment/Payload.js new file mode 100644 index 0000000000..3c77533fd6 --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Payload.js @@ -0,0 +1,727 @@ +import { customerAcceptance } from "./Commons"; + +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "12", + card_exp_year: "25", + card_holder_name: "John Doe", + card_cvc: "123", +}; + +// Note: Payload may not support 3DS authentication - using same card for consistency +const successfulThreeDSTestCardDetails = { + card_number: "4242424242424242", + card_exp_month: "12", + card_exp_year: "25", + card_holder_name: "John Doe", + card_cvc: "123", +}; + +const failedNo3DSCardDetails = { + card_number: "4000000000000002", + card_exp_month: "01", + card_exp_year: "25", + card_holder_name: "John Doe", + card_cvc: "123", +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const payment_method_data_no3ds = { + card: { + last4: "4242", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "STRIPE PAYMENTS UK LIMITED", + card_issuing_country: "UNITEDKINGDOM", + card_isin: "424242", + card_extended_bin: null, + card_exp_month: "12", + card_exp_year: "25", + card_holder_name: "John Doe", + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "on_session", + }, + }, + }, + PaymentIntentOffSession: { + Request: { + currency: "USD", + customer_acceptance: null, + amount: 6000, + authentication_type: "no_three_ds", + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + SessionToken: { + Response: { + status: 200, + body: { + session_token: [], + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6000, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6050, + amount: 6000, + net_amount: 6050, + }, + }, + }, + "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "3DS authentication is not supported by Payload", + code: "IR_00", + }, + }, + }, + }, + "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "3DS authentication is not supported by Payload", + code: "IR_00", + }, + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", // Manual capture should require explicit capture + payment_method: "card", + attempt_count: 1, + }, + }, + }, + No3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + attempt_count: 1, + }, + }, + }, + No3DSFailPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: failedNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "failed", + error_code: "card_declined", + error_message: "Your card was declined", + unified_code: "UE_9000", + unified_message: "Something went wrong", + }, + }, + }, + Capture: { + Request: { + amount_to_capture: 6000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6000, + amount_capturable: 0, + amount_received: 6000, + }, + }, + }, + PartialCapture: { + Request: { + amount_to_capture: 2000, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6000, + amount_capturable: 0, + amount_received: 2000, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + amount: 6000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + amount: 6000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", // Keep this as requires_capture for manual flows + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUse3DSAutoCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSessionWithoutBilling: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, + Request: { + setup_future_usage: "off_session", + billing: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + mandate_id: null, + payment_method_data: payment_method_data_no3ds, + payment_method: "card", + connector: "payload", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + mandate_id: null, + payment_method_data: payment_method_data_no3ds, + payment_method: "card", + connector: "payload", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + payment_method_data: payment_method_data_no3ds, + payment_method: "card", + connector: "payload", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + payment_method_data: payment_method_data_no3ds, + payment_method: "card", + connector: "payload", + }, + }, + }, + ZeroAuthPaymentIntent: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + currency: "BRL", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + ZeroAuthMandate: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + ZeroAuthConfirmPayment: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + TRIGGER_SKIP: true, + }, + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + setup_future_usage: "off_session", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index a551143242..a82f9fca26 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -37,6 +37,7 @@ import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; import { connectorDetails as noonConnectorDetails } from "./Noon.js"; import { connectorDetails as novalnetConnectorDetails } from "./Novalnet.js"; import { connectorDetails as payboxConnectorDetails } from "./Paybox.js"; +import { connectorDetails as payloadConnectorDetails } from "./Payload.js"; import { connectorDetails as paypalConnectorDetails } from "./Paypal.js"; import { connectorDetails as powertranzConnectorDetails } from "./PowerTranz.js"; import { connectorDetails as redsysConnectorDetails } from "./Redsys.js"; @@ -85,6 +86,7 @@ const connectorDetails = { noon: noonConnectorDetails, novalnet: novalnetConnectorDetails, paybox: payboxConnectorDetails, + payload: payloadConnectorDetails, paypal: paypalConnectorDetails, powertranz: powertranzConnectorDetails, redsys: redsysConnectorDetails, @@ -364,6 +366,7 @@ export const CONNECTOR_LISTS = { "fiuu", "jpmorgan", "nexinets", + "payload", "paypal", "stax", "wellsfargo",