mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 03:13:56 +08:00
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
414 lines
11 KiB
Rust
414 lines
11 KiB
Rust
#![allow(
|
|
dead_code,
|
|
clippy::expect_used,
|
|
clippy::missing_panics_doc,
|
|
clippy::unwrap_used
|
|
)]
|
|
|
|
use actix_http::{body::MessageBody, Request};
|
|
use actix_web::{
|
|
dev::{Service, ServiceResponse},
|
|
test::{call_and_read_body_json, TestRequest},
|
|
};
|
|
use derive_deref::Deref;
|
|
use router::{configs::settings::Settings, routes::AppState, services};
|
|
use router_env::tracing::Instrument;
|
|
use serde::{de::DeserializeOwned, Deserialize};
|
|
use serde_json::{json, Value};
|
|
use tokio::sync::{oneshot, OnceCell};
|
|
|
|
static SERVER: OnceCell<bool> = OnceCell::const_new();
|
|
|
|
async fn spawn_server() -> bool {
|
|
let conf = Settings::new().expect("invalid settings");
|
|
let server = Box::pin(router::start_server(conf))
|
|
.await
|
|
.expect("failed to create server");
|
|
|
|
let _server = tokio::spawn(server.in_current_span());
|
|
true
|
|
}
|
|
|
|
pub async fn setup() {
|
|
Box::pin(SERVER.get_or_init(spawn_server)).await;
|
|
}
|
|
|
|
const STRIPE_MOCK: &str = "http://localhost:12111/";
|
|
|
|
async fn stripemock() -> Option<String> {
|
|
// not working: https://github.com/stripe/stripe-mock/issues/231
|
|
None
|
|
}
|
|
|
|
pub async fn mk_service(
|
|
) -> impl Service<Request, Response = ServiceResponse<impl MessageBody>, Error = actix_web::Error> {
|
|
let mut conf = Settings::new().unwrap();
|
|
let request_body_limit = conf.server.request_body_limit;
|
|
|
|
if let Some(url) = stripemock().await {
|
|
conf.connectors.stripe.base_url = url;
|
|
}
|
|
let tx: oneshot::Sender<()> = oneshot::channel().0;
|
|
|
|
let app_state = Box::pin(AppState::with_storage(
|
|
conf,
|
|
router::db::StorageImpl::Mock,
|
|
tx,
|
|
Box::new(services::MockApiClient),
|
|
))
|
|
.await;
|
|
actix_web::test::init_service(router::mk_app(app_state, request_body_limit)).await
|
|
}
|
|
|
|
pub struct Guest;
|
|
|
|
pub struct Admin {
|
|
authkey: String,
|
|
}
|
|
|
|
pub struct User {
|
|
authkey: String,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub struct AppClient<T> {
|
|
state: T,
|
|
}
|
|
|
|
impl AppClient<Guest> {
|
|
pub fn guest() -> Self {
|
|
Self { state: Guest }
|
|
}
|
|
}
|
|
|
|
impl AppClient<Admin> {
|
|
pub async fn create_merchant_account<T: DeserializeOwned, S, B>(
|
|
&self,
|
|
app: &S,
|
|
merchant_id: impl Into<Option<String>>,
|
|
) -> T
|
|
where
|
|
S: Service<Request, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
B: MessageBody,
|
|
{
|
|
let request = TestRequest::post()
|
|
.uri("/accounts")
|
|
.append_header(("api-key".to_owned(), self.state.authkey.clone()))
|
|
.set_json(mk_merchant_account(merchant_id.into()))
|
|
.to_request();
|
|
|
|
call_and_read_body_json(app, request).await
|
|
}
|
|
|
|
pub async fn create_connector<T: DeserializeOwned, S, B>(
|
|
&self,
|
|
app: &S,
|
|
merchant_id: &common_utils::id_type::MerchantId,
|
|
connector_name: &str,
|
|
api_key: &str,
|
|
) -> T
|
|
where
|
|
S: Service<Request, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
B: MessageBody,
|
|
{
|
|
let request = TestRequest::post()
|
|
.uri(&format!(
|
|
"/account/{}/connectors",
|
|
merchant_id.get_string_repr()
|
|
))
|
|
.append_header(("api-key".to_owned(), self.state.authkey.clone()))
|
|
.set_json(mk_connector(connector_name, api_key))
|
|
.to_request();
|
|
|
|
call_and_read_body_json(app, request).await
|
|
}
|
|
}
|
|
|
|
impl AppClient<User> {
|
|
pub async fn create_payment<T: DeserializeOwned, S, B>(
|
|
&self,
|
|
app: &S,
|
|
amount: i64,
|
|
amount_to_capture: i32,
|
|
) -> T
|
|
where
|
|
S: Service<Request, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
B: MessageBody,
|
|
{
|
|
let request = TestRequest::post()
|
|
.uri("/payments")
|
|
.append_header(("api-key".to_owned(), self.state.authkey.clone()))
|
|
.set_json(mk_payment(amount, amount_to_capture))
|
|
.to_request();
|
|
call_and_read_body_json(app, request).await
|
|
}
|
|
|
|
pub async fn create_refund<T: DeserializeOwned, S, B>(
|
|
&self,
|
|
app: &S,
|
|
payment_id: &common_utils::id_type::PaymentId,
|
|
amount: usize,
|
|
) -> T
|
|
where
|
|
S: Service<Request, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
B: MessageBody,
|
|
{
|
|
let request = TestRequest::post()
|
|
.uri("/refunds")
|
|
.append_header(("api-key".to_owned(), self.state.authkey.clone()))
|
|
.set_json(mk_refund(payment_id, amount))
|
|
.to_request();
|
|
call_and_read_body_json(app, request).await
|
|
}
|
|
}
|
|
|
|
impl<T> AppClient<T> {
|
|
pub fn admin(&self, authkey: &str) -> AppClient<Admin> {
|
|
AppClient {
|
|
state: Admin {
|
|
authkey: authkey.to_string(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn user(&self, authkey: &str) -> AppClient<User> {
|
|
AppClient {
|
|
state: User {
|
|
authkey: authkey.to_string(),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub async fn health<S, B>(&self, app: &S) -> String
|
|
where
|
|
S: Service<Request, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
|
B: MessageBody,
|
|
{
|
|
let request = TestRequest::get().uri("/health").to_request();
|
|
let bytes = actix_web::test::call_and_read_body(app, request).await;
|
|
String::from_utf8(bytes.to_vec()).unwrap()
|
|
}
|
|
}
|
|
|
|
fn mk_merchant_account(merchant_id: Option<String>) -> Value {
|
|
let merchant_id = merchant_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
|
|
|
json!({
|
|
"merchant_id": merchant_id,
|
|
"merchant_name": "NewAge Retailer",
|
|
"merchant_details": {
|
|
"primary_contact_person": "John Test",
|
|
"primary_email": "JohnTest@test.com",
|
|
"primary_phone": "sunt laborum",
|
|
"secondary_contact_person": "John Test2",
|
|
"secondary_email": "JohnTest2@test.com",
|
|
"secondary_phone": "cillum do dolor id",
|
|
"website": "www.example.com",
|
|
"about_business": "Online Retail with a wide selection of organic products for North America",
|
|
"address": {
|
|
"line1": "Juspay Router",
|
|
"line2": "Koramangala",
|
|
"line3": "Stallion",
|
|
"city": "Bangalore",
|
|
"state": "Karnataka",
|
|
"zip": "560095",
|
|
"country": "IN"
|
|
}
|
|
},
|
|
"return_url": "www.example.com/success",
|
|
"webhook_details": {
|
|
"webhook_version": "1.0.1",
|
|
"webhook_username": "ekart_retail",
|
|
"webhook_password": "password_ekart@123",
|
|
},
|
|
"routing_algorithm": {
|
|
"type": "single",
|
|
"data": "stripe"
|
|
},
|
|
"sub_merchants_enabled": false,
|
|
"metadata": {
|
|
"city": "NY",
|
|
"unit": "245"
|
|
}
|
|
})
|
|
}
|
|
|
|
fn mk_payment(amount: i64, amount_to_capture: i32) -> Value {
|
|
json!({
|
|
"amount": amount,
|
|
"currency": "USD",
|
|
"confirm": true,
|
|
"capture_method": "automatic",
|
|
"capture_on": "2022-10-10T10:11:12Z",
|
|
"amount_to_capture": amount_to_capture,
|
|
"customer_id": "cus_udst2tfldj6upmye2reztkmm4i",
|
|
"email": "guest@example.com",
|
|
"name": "John Doe",
|
|
"phone": "999999999",
|
|
"phone_country_code": "+65",
|
|
"description": "Its my first payment request",
|
|
"authentication_type": "no_three_ds",
|
|
"payment_method": "card",
|
|
"payment_method_data": {
|
|
"card": {
|
|
"card_number": "4242424242424242",
|
|
"card_exp_month": "10",
|
|
"card_exp_year": "35",
|
|
"card_holder_name": "John Doe",
|
|
"card_cvc": "123"
|
|
}
|
|
},
|
|
"statement_descriptor_name": "Hyperswitch",
|
|
"statement_descriptor_suffix": "Hyperswitch",
|
|
"metadata": {
|
|
"udf1": "value1",
|
|
"new_customer": "true",
|
|
"login_date": "2019-09-10T10:11:12Z"
|
|
}
|
|
})
|
|
}
|
|
|
|
fn mk_connector(connector_name: &str, api_key: &str) -> Value {
|
|
json!({
|
|
"connector_type": "fiz_operations",
|
|
"connector_name": connector_name,
|
|
"connector_account_details": {
|
|
"auth_type": "HeaderKey",
|
|
"api_key": api_key,
|
|
},
|
|
"test_mode": false,
|
|
"disabled": false,
|
|
"payment_methods_enabled": [
|
|
{
|
|
"payment_method": "wallet",
|
|
"payment_method_types": [
|
|
"upi_collect",
|
|
"upi_intent"
|
|
],
|
|
"payment_method_issuers": [
|
|
"labore magna ipsum",
|
|
"aute"
|
|
],
|
|
"payment_schemes": [
|
|
"Discover",
|
|
"Discover"
|
|
],
|
|
"accepted_currencies": [
|
|
"AED",
|
|
"AED"
|
|
],
|
|
"accepted_countries": [
|
|
"in",
|
|
"us"
|
|
],
|
|
"minimum_amount": 1,
|
|
"maximum_amount": 68607706,
|
|
"recurring_enabled": true,
|
|
"installment_payment_enabled": true
|
|
}
|
|
],
|
|
"metadata": {
|
|
"city": "NY",
|
|
"unit": "245"
|
|
}
|
|
})
|
|
}
|
|
|
|
fn _mk_payment_confirm() -> Value {
|
|
json!({
|
|
"return_url": "http://example.com/payments",
|
|
"setup_future_usage": "on_session",
|
|
"authentication_type": "no_three_ds",
|
|
"payment_method": "card",
|
|
"payment_method_data": {
|
|
"card": {
|
|
"card_number": "4242424242424242",
|
|
"card_exp_month": "10",
|
|
"card_exp_year": "35",
|
|
"card_holder_name": "John Doe",
|
|
"card_cvc": "123"
|
|
}
|
|
},
|
|
"shipping": {},
|
|
"billing": {}
|
|
})
|
|
}
|
|
|
|
fn mk_refund(payment_id: &common_utils::id_type::PaymentId, amount: usize) -> Value {
|
|
let timestamp = common_utils::date_time::now().to_string();
|
|
|
|
json!({
|
|
"payment_id": payment_id,
|
|
"refund_id": timestamp.get(23..),
|
|
"amount": amount,
|
|
"reason": "Customer returned product",
|
|
"metadata": {
|
|
"udf1": "value1",
|
|
"new_customer": "true",
|
|
"login_date": "2019-09-10T10:11:12Z"
|
|
}
|
|
})
|
|
}
|
|
|
|
pub struct HNil;
|
|
|
|
impl<'de> Deserialize<'de> for HNil {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
deserializer.deserialize_any(serde::de::IgnoredAny)?;
|
|
Ok(Self)
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct HCons<H, T> {
|
|
#[serde(flatten)]
|
|
pub head: H,
|
|
#[serde(flatten)]
|
|
pub tail: T,
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! HList {
|
|
() => { $crate::utils::HNil };
|
|
($head:ty $(, $rest:ty)* $(,)?) => { $crate::utils::HCons<$head, HList![$($rest),*]> };
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! hlist_pat {
|
|
() => { $crate::utils::HNil };
|
|
($head:pat $(, $rest:pat)* $(,)?) => { $crate::utils::HCons { head: $head, tail: hlist_pat!($($rest),*) } };
|
|
}
|
|
|
|
#[derive(Deserialize, Deref)]
|
|
pub struct MerchantId {
|
|
merchant_id: common_utils::id_type::MerchantId,
|
|
}
|
|
|
|
#[derive(Deserialize, Deref)]
|
|
pub struct ApiKey {
|
|
api_key: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct Error {
|
|
pub message: Message,
|
|
}
|
|
|
|
#[derive(Deserialize, Deref)]
|
|
pub struct Message {
|
|
message: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Deref)]
|
|
pub struct PaymentId {
|
|
payment_id: common_utils::id_type::PaymentId,
|
|
}
|
|
|
|
#[derive(Deserialize, Deref)]
|
|
pub struct Status {
|
|
status: String,
|
|
}
|