Files
Anurag 071b0732d1 feat(core): add all_keys_required in confirm and psync payload (#7998)
Co-authored-by: Anurag Singh <anurag.singh.001@Anurag-Singh-WPMHJ5619X.local>
Co-authored-by: Anurag Singh <anurag.singh.001@MacBookPro.lan>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
2025-05-19 13:27:43 +00:00

449 lines
15 KiB
Rust

#![allow(clippy::print_stdout)]
use std::{borrow::Cow, marker::PhantomData, str::FromStr, sync::Arc};
use common_utils::id_type;
use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails};
use masking::Secret;
use router::{
configs::settings::Settings,
core::payments,
db::StorageImpl,
routes, services,
types::{self, storage::enums, PaymentAddress},
};
use tokio::sync::oneshot;
use crate::{connector_auth::ConnectorAuthentication, utils};
fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
let auth = ConnectorAuthentication::new()
.aci
.expect("Missing ACI connector authentication configuration");
let merchant_id = id_type::MerchantId::try_from(Cow::from("aci")).unwrap();
types::RouterData {
flow: PhantomData,
merchant_id,
customer_id: Some(id_type::CustomerId::try_from(Cow::from("aci")).unwrap()),
tenant_id: id_type::TenantId::try_from_string("public".to_string()).unwrap(),
connector: "aci".to_string(),
payment_id: uuid::Uuid::new_v4().to_string(),
attempt_id: uuid::Uuid::new_v4().to_string(),
status: enums::AttemptStatus::default(),
auth_type: enums::AuthenticationType::NoThreeDs,
payment_method: enums::PaymentMethod::Card,
connector_auth_type: utils::to_connector_auth_type(auth.into()),
description: Some("This is a test".to_string()),
payment_method_status: None,
request: types::PaymentsAuthorizeData {
amount: 1000,
currency: enums::Currency::USD,
payment_method_data: types::domain::PaymentMethodData::Card(types::domain::Card {
card_number: cards::CardNumber::from_str("4200000000000000").unwrap(),
card_exp_month: Secret::new("10".to_string()),
card_exp_year: Secret::new("2025".to_string()),
card_cvc: Secret::new("999".to_string()),
card_issuer: None,
card_network: None,
card_type: None,
card_issuing_country: None,
bank_code: None,
nick_name: Some(Secret::new("nick_name".into())),
card_holder_name: Some(Secret::new("card holder name".into())),
co_badged_card_data: None,
}),
confirm: true,
statement_descriptor_suffix: None,
statement_descriptor: None,
setup_future_usage: None,
mandate_id: None,
off_session: None,
setup_mandate_details: None,
capture_method: None,
browser_info: None,
order_details: None,
order_category: None,
email: None,
customer_name: None,
session_token: None,
enrolled_for_3ds: false,
related_transaction_id: None,
payment_experience: None,
payment_method_type: None,
router_return_url: None,
webhook_url: None,
complete_authorize_url: None,
customer_id: None,
surcharge_details: None,
request_incremental_authorization: false,
metadata: None,
authentication_data: None,
customer_acceptance: None,
..utils::PaymentAuthorizeType::default().0
},
response: Err(types::ErrorResponse::default()),
address: PaymentAddress::new(
None,
None,
Some(Address {
address: Some(AddressDetails {
first_name: Some(Secret::new("John".to_string())),
last_name: Some(Secret::new("Doe".to_string())),
..Default::default()
}),
phone: Some(PhoneDetails {
number: Some(Secret::new("9123456789".to_string())),
country_code: Some("+351".to_string()),
}),
email: None,
}),
None,
),
connector_meta_data: None,
connector_wallets_details: None,
amount_captured: None,
minor_amount_captured: None,
access_token: None,
session_token: None,
reference_id: None,
payment_method_token: None,
connector_customer: None,
recurring_mandate_payment_data: None,
connector_response: None,
preprocessing_id: None,
connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
#[cfg(feature = "payouts")]
payout_method_data: None,
#[cfg(feature = "payouts")]
quote_id: None,
test_mode: None,
payment_method_balance: None,
connector_api_version: None,
connector_http_status_code: None,
apple_pay_flow: None,
external_latency: None,
frm_metadata: None,
refund_id: None,
dispute_id: None,
integrity_check: Ok(()),
additional_merchant_data: None,
header_payload: None,
connector_mandate_request_reference_id: None,
authentication_id: None,
psd2_sca_exemption_type: None,
whole_connector_response: None,
}
}
fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
let auth = ConnectorAuthentication::new()
.aci
.expect("Missing ACI connector authentication configuration");
let merchant_id = id_type::MerchantId::try_from(Cow::from("aci")).unwrap();
types::RouterData {
flow: PhantomData,
merchant_id,
customer_id: Some(id_type::CustomerId::try_from(Cow::from("aci")).unwrap()),
tenant_id: id_type::TenantId::try_from_string("public".to_string()).unwrap(),
connector: "aci".to_string(),
payment_id: uuid::Uuid::new_v4().to_string(),
attempt_id: uuid::Uuid::new_v4().to_string(),
payment_method_status: None,
status: enums::AttemptStatus::default(),
payment_method: enums::PaymentMethod::Card,
auth_type: enums::AuthenticationType::NoThreeDs,
connector_auth_type: utils::to_connector_auth_type(auth.into()),
description: Some("This is a test".to_string()),
request: types::RefundsData {
payment_amount: 1000,
currency: enums::Currency::USD,
refund_id: uuid::Uuid::new_v4().to_string(),
connector_transaction_id: String::new(),
refund_amount: 100,
webhook_url: None,
connector_metadata: None,
reason: None,
connector_refund_id: None,
browser_info: None,
..utils::PaymentRefundType::default().0
},
response: Err(types::ErrorResponse::default()),
address: PaymentAddress::default(),
connector_meta_data: None,
connector_wallets_details: None,
amount_captured: None,
minor_amount_captured: None,
access_token: None,
session_token: None,
reference_id: None,
payment_method_token: None,
connector_customer: None,
recurring_mandate_payment_data: None,
connector_response: None,
preprocessing_id: None,
connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
#[cfg(feature = "payouts")]
payout_method_data: None,
#[cfg(feature = "payouts")]
quote_id: None,
test_mode: None,
payment_method_balance: None,
connector_api_version: None,
connector_http_status_code: None,
apple_pay_flow: None,
external_latency: None,
frm_metadata: None,
refund_id: None,
dispute_id: None,
integrity_check: Ok(()),
additional_merchant_data: None,
header_payload: None,
connector_mandate_request_reference_id: None,
authentication_id: None,
psd2_sca_exemption_type: None,
whole_connector_response: None,
}
}
#[actix_web::test]
async fn payments_create_success() {
let conf = Settings::new().unwrap();
let tx: oneshot::Sender<()> = oneshot::channel().0;
let app_state = Box::pin(routes::AppState::with_storage(
conf,
StorageImpl::PostgresqlTest,
tx,
Box::new(services::MockApiClient),
))
.await;
let state = Arc::new(app_state)
.get_session_state(
&id_type::TenantId::try_from_string("public".to_string()).unwrap(),
None,
|| {},
)
.unwrap();
use router::connector::Aci;
let connector = utils::construct_connector_data_old(
Box::new(Aci::new()),
types::Connector::Aci,
types::api::GetToken::Connector,
None,
);
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
types::api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
let request = construct_payment_router_data();
let response = services::api::execute_connector_processing_step(
&state,
connector_integration,
&request,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.unwrap();
assert!(
response.status == enums::AttemptStatus::Charged,
"The payment failed"
);
}
#[actix_web::test]
#[ignore]
async fn payments_create_failure() {
{
let conf = Settings::new().unwrap();
use router::connector::Aci;
let tx: oneshot::Sender<()> = oneshot::channel().0;
let app_state = Box::pin(routes::AppState::with_storage(
conf,
StorageImpl::PostgresqlTest,
tx,
Box::new(services::MockApiClient),
))
.await;
let state = Arc::new(app_state)
.get_session_state(
&id_type::TenantId::try_from_string("public".to_string()).unwrap(),
None,
|| {},
)
.unwrap();
let connector = utils::construct_connector_data_old(
Box::new(Aci::new()),
types::Connector::Aci,
types::api::GetToken::Connector,
None,
);
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
types::api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
let mut request = construct_payment_router_data();
request.request.payment_method_data =
types::domain::PaymentMethodData::Card(types::domain::Card {
card_number: cards::CardNumber::from_str("4200000000000000").unwrap(),
card_exp_month: Secret::new("10".to_string()),
card_exp_year: Secret::new("2025".to_string()),
card_cvc: Secret::new("99".to_string()),
card_issuer: None,
card_network: None,
card_type: None,
card_issuing_country: None,
bank_code: None,
nick_name: Some(Secret::new("nick_name".into())),
card_holder_name: Some(Secret::new("card holder name".into())),
co_badged_card_data: None,
});
let response = services::api::execute_connector_processing_step(
&state,
connector_integration,
&request,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.is_err();
println!("{response:?}");
assert!(response, "The payment was intended to fail but it passed");
}
}
#[actix_web::test]
async fn refund_for_successful_payments() {
let conf = Settings::new().unwrap();
use router::connector::Aci;
let connector = utils::construct_connector_data_old(
Box::new(Aci::new()),
types::Connector::Aci,
types::api::GetToken::Connector,
None,
);
let tx: oneshot::Sender<()> = oneshot::channel().0;
let app_state = Box::pin(routes::AppState::with_storage(
conf,
StorageImpl::PostgresqlTest,
tx,
Box::new(services::MockApiClient),
))
.await;
let state = Arc::new(app_state)
.get_session_state(
&id_type::TenantId::try_from_string("public".to_string()).unwrap(),
None,
|| {},
)
.unwrap();
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
types::api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
let request = construct_payment_router_data();
let response = services::api::execute_connector_processing_step(
&state,
connector_integration,
&request,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.unwrap();
assert!(
response.status == enums::AttemptStatus::Charged,
"The payment failed"
);
let connector_integration: services::BoxedRefundConnectorIntegrationInterface<
types::api::Execute,
types::RefundsData,
types::RefundsResponseData,
> = connector.connector.get_connector_integration();
let mut refund_request = construct_refund_router_data();
refund_request.request.connector_transaction_id = match response.response.unwrap() {
types::PaymentsResponseData::TransactionResponse { resource_id, .. } => {
resource_id.get_connector_transaction_id().unwrap()
}
_ => panic!("Connector transaction id not found"),
};
let response = services::api::execute_connector_processing_step(
&state,
connector_integration,
&refund_request,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.unwrap();
println!("{response:?}");
assert!(
response.response.unwrap().refund_status == enums::RefundStatus::Success,
"The refund transaction failed"
);
}
#[actix_web::test]
#[ignore]
async fn refunds_create_failure() {
let conf = Settings::new().unwrap();
use router::connector::Aci;
let connector = utils::construct_connector_data_old(
Box::new(Aci::new()),
types::Connector::Aci,
types::api::GetToken::Connector,
None,
);
let tx: oneshot::Sender<()> = oneshot::channel().0;
let app_state = Box::pin(routes::AppState::with_storage(
conf,
StorageImpl::PostgresqlTest,
tx,
Box::new(services::MockApiClient),
))
.await;
let state = Arc::new(app_state)
.get_session_state(
&id_type::TenantId::try_from_string("public".to_string()).unwrap(),
None,
|| {},
)
.unwrap();
let connector_integration: services::BoxedRefundConnectorIntegrationInterface<
types::api::Execute,
types::RefundsData,
types::RefundsResponseData,
> = connector.connector.get_connector_integration();
let mut request = construct_refund_router_data();
request.request.connector_transaction_id = "1234".to_string();
let response = services::api::execute_connector_processing_step(
&state,
connector_integration,
&request,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.is_err();
println!("{response:?}");
assert!(response, "The refund was intended to fail but it passed");
}