diff --git a/apps/labrinth/.env b/apps/labrinth/.env index af01d17a1..ecc4df48f 100644 --- a/apps/labrinth/.env +++ b/apps/labrinth/.env @@ -68,6 +68,9 @@ PAYPAL_API_URL=https://api-m.sandbox.paypal.com/v1/ PAYPAL_WEBHOOK_ID=none PAYPAL_CLIENT_ID=none PAYPAL_CLIENT_SECRET=none +PAYPAL_NVP_USERNAME=none +PAYPAL_NVP_PASSWORD=none +PAYPAL_NVP_SIGNATURE=none STEAM_API_KEY=none @@ -106,4 +109,7 @@ STRIPE_WEBHOOK_SECRET=none ADITUDE_API_KEY=none -PYRO_API_KEY=none \ No newline at end of file +PYRO_API_KEY=none + +BREX_API_URL=https://platform.brexapis.com/v2/ +BREX_API_KEY=none \ No newline at end of file diff --git a/apps/labrinth/.sqlx/query-109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b.json b/apps/labrinth/.sqlx/query-109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b.json index c37d8c114..ab8f19f4b 100644 --- a/apps/labrinth/.sqlx/query-109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b.json +++ b/apps/labrinth/.sqlx/query-109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b.json @@ -99,7 +99,7 @@ false, true, true, - false + true ] }, "hash": "109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b" diff --git a/apps/labrinth/.sqlx/query-6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634.json b/apps/labrinth/.sqlx/query-6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634.json index 232d3662c..950d0c724 100644 --- a/apps/labrinth/.sqlx/query-6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634.json +++ b/apps/labrinth/.sqlx/query-6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634.json @@ -99,7 +99,7 @@ false, true, true, - false + true ] }, "hash": "6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634" diff --git a/apps/labrinth/.sqlx/query-7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc.json b/apps/labrinth/.sqlx/query-7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc.json index 663b45ab5..7c9bd5f4e 100644 --- a/apps/labrinth/.sqlx/query-7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc.json +++ b/apps/labrinth/.sqlx/query-7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc.json @@ -99,7 +99,7 @@ false, true, true, - false + true ] }, "hash": "7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc" diff --git a/apps/labrinth/.sqlx/query-8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1.json b/apps/labrinth/.sqlx/query-8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1.json index 91ab60c98..d28e9a721 100644 --- a/apps/labrinth/.sqlx/query-8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1.json +++ b/apps/labrinth/.sqlx/query-8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1.json @@ -99,7 +99,7 @@ false, true, true, - false + true ] }, "hash": "8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1" diff --git a/apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json b/apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json index b755721ad..c7e1c1a3c 100644 --- a/apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json +++ b/apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json @@ -99,7 +99,7 @@ false, true, true, - false + true ] }, "hash": "99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2" diff --git a/apps/labrinth/.sqlx/query-bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b.json b/apps/labrinth/.sqlx/query-bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b.json index 9045739c4..6f4cba220 100644 --- a/apps/labrinth/.sqlx/query-bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b.json +++ b/apps/labrinth/.sqlx/query-bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b.json @@ -99,7 +99,7 @@ false, true, true, - false + true ] }, "hash": "bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b" diff --git a/apps/labrinth/src/lib.rs b/apps/labrinth/src/lib.rs index f54494147..98157db5d 100644 --- a/apps/labrinth/src/lib.rs +++ b/apps/labrinth/src/lib.rs @@ -448,6 +448,9 @@ pub fn check_env_vars() -> bool { failed |= check_var::("PAYPAL_WEBHOOK_ID"); failed |= check_var::("PAYPAL_CLIENT_ID"); failed |= check_var::("PAYPAL_CLIENT_SECRET"); + failed |= check_var::("PAYPAL_NVP_USERNAME"); + failed |= check_var::("PAYPAL_NVP_PASSWORD"); + failed |= check_var::("PAYPAL_NVP_SIGNATURE"); failed |= check_var::("HCAPTCHA_SECRET"); @@ -482,9 +485,11 @@ pub fn check_env_vars() -> bool { failed |= check_var::("STRIPE_API_KEY"); failed |= check_var::("STRIPE_WEBHOOK_SECRET"); - failed |= check_var::("ADITUDE_API_KEY"); + failed |= check_var::("ADITUDE_API_KEY"); failed |= check_var::("PYRO_API_KEY"); + failed |= check_var::("BREX_API_KEY"); + failed } diff --git a/apps/labrinth/src/queue/payouts.rs b/apps/labrinth/src/queue/payouts.rs index adc40b18c..38fd54a16 100644 --- a/apps/labrinth/src/queue/payouts.rs +++ b/apps/labrinth/src/queue/payouts.rs @@ -23,7 +23,7 @@ pub struct PayoutsQueue { payout_options: RwLock>, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct PayPalCredentials { access_token: String, token_type: String, @@ -36,6 +36,12 @@ struct PayoutMethods { expires: DateTime, } +#[derive(Serialize)] +pub struct AccountBalance { + pub available: Decimal, + pub pending: Decimal, +} + impl Default for PayoutsQueue { fn default() -> Self { Self::new() @@ -545,6 +551,136 @@ impl PayoutsQueue { Ok(options.options) } + + pub async fn get_brex_balance() -> Result, ApiError> + { + #[derive(Deserialize)] + struct BrexBalance { + pub amount: i64, + // pub currency: String, + } + + #[derive(Deserialize)] + struct BrexAccount { + pub current_balance: BrexBalance, + pub available_balance: BrexBalance, + } + + #[derive(Deserialize)] + struct BrexResponse { + pub items: Vec, + } + + let client = reqwest::Client::new(); + let res = client + .get(format!("{}accounts/cash", dotenvy::var("BREX_API_URL")?)) + .bearer_auth(&dotenvy::var("BREX_API_KEY")?) + .send() + .await? + .json::() + .await?; + + Ok(Some(AccountBalance { + available: Decimal::from( + res.items + .iter() + .map(|x| x.available_balance.amount) + .sum::(), + ) / Decimal::from(100), + pending: Decimal::from( + res.items + .iter() + .map(|x| { + x.current_balance.amount - x.available_balance.amount + }) + .sum::(), + ) / Decimal::from(100), + })) + } + + pub async fn get_paypal_balance() -> Result, ApiError> + { + let api_username = dotenvy::var("PAYPAL_NVP_USERNAME")?; + let api_password = dotenvy::var("PAYPAL_NVP_PASSWORD")?; + let api_signature = dotenvy::var("PAYPAL_NVP_SIGNATURE")?; + + let mut params = HashMap::new(); + params.insert("METHOD", "GetBalance"); + params.insert("VERSION", "204"); + params.insert("USER", &api_username); + params.insert("PWD", &api_password); + params.insert("SIGNATURE", &api_signature); + params.insert("RETURNALLCURRENCIES", "1"); + + let endpoint = "https://api-3t.paypal.com/nvp"; + + let client = reqwest::Client::new(); + let response = client.post(endpoint).form(¶ms).send().await?; + + let text = response.text().await?; + let body = urlencoding::decode(&text).unwrap_or_default(); + + let mut key_value_map = HashMap::new(); + + for pair in body.split('&') { + let mut iter = pair.splitn(2, '='); + if let (Some(key), Some(value)) = (iter.next(), iter.next()) { + key_value_map.insert(key.to_string(), value.to_string()); + } + } + + if let Some(amount) = key_value_map + .get("L_AMT0") + .and_then(|x| Decimal::from_str_exact(x).ok()) + { + Ok(Some(AccountBalance { + available: amount, + pending: Decimal::ZERO, + })) + } else { + Ok(None) + } + } + + pub async fn get_tremendous_balance( + &self, + ) -> Result, ApiError> { + #[derive(Deserialize)] + struct FundingSourceMeta { + available_cents: u64, + pending_cents: u64, + } + + #[derive(Deserialize)] + struct FundingSource { + method: String, + meta: FundingSourceMeta, + } + + #[derive(Deserialize)] + struct FundingSourceRequest { + pub funding_sources: Vec, + } + + let val = self + .make_tremendous_request::<(), FundingSourceRequest>( + Method::GET, + "funding_sources", + None, + ) + .await?; + + Ok(val + .funding_sources + .into_iter() + .find(|x| x.method == "balance") + .map(|x| AccountBalance { + available: Decimal::from(x.meta.available_cents) + / Decimal::from(100), + pending: Decimal::from(x.meta.pending_cents) + / Decimal::from(100), + })) + } } #[derive(Deserialize)] diff --git a/apps/labrinth/src/routes/internal/admin.rs b/apps/labrinth/src/routes/internal/admin.rs index 68987d1da..3a39449f9 100644 --- a/apps/labrinth/src/routes/internal/admin.rs +++ b/apps/labrinth/src/routes/internal/admin.rs @@ -5,12 +5,13 @@ use crate::models::ids::ProjectId; use crate::models::pats::Scopes; use crate::queue::analytics::AnalyticsQueue; use crate::queue::maxmind::MaxMindIndexer; +use crate::queue::payouts::PayoutsQueue; use crate::queue::session::AuthQueue; use crate::routes::ApiError; use crate::search::SearchConfig; use crate::util::date::get_current_tenths_of_ms; use crate::util::guards::admin_key_guard; -use actix_web::{patch, post, web, HttpRequest, HttpResponse}; +use actix_web::{get, patch, post, web, HttpRequest, HttpResponse}; use serde::Deserialize; use sqlx::PgPool; use std::collections::HashMap; @@ -21,7 +22,8 @@ pub fn config(cfg: &mut web::ServiceConfig) { cfg.service( web::scope("admin") .service(count_download) - .service(force_reindex), + .service(force_reindex) + .service(get_balances), ); } @@ -158,3 +160,21 @@ pub async fn force_reindex( index_projects(pool.as_ref().clone(), redis.clone(), &config).await?; Ok(HttpResponse::NoContent().finish()) } + +#[get("/_balances", guard = "admin_key_guard")] +pub async fn get_balances( + payouts: web::Data, +) -> Result { + let (paypal, brex, tremendous) = futures::future::try_join3( + PayoutsQueue::get_paypal_balance(), + PayoutsQueue::get_brex_balance(), + payouts.get_tremendous_balance(), + ) + .await?; + + Ok(HttpResponse::Ok().json(serde_json::json!({ + "paypal": paypal, + "brex": brex, + "tremendous": tremendous, + }))) +}