1
0

Add balance route (#3107)

This commit is contained in:
Jai Agrawal
2024-12-30 20:17:51 -07:00
committed by GitHub
parent 01fe08f079
commit bb3de4b74b
10 changed files with 178 additions and 11 deletions

View File

@@ -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
PYRO_API_KEY=none
BREX_API_URL=https://platform.brexapis.com/v2/
BREX_API_KEY=none

View File

@@ -99,7 +99,7 @@
false,
true,
true,
false
true
]
},
"hash": "109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b"

View File

@@ -99,7 +99,7 @@
false,
true,
true,
false
true
]
},
"hash": "6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634"

View File

@@ -99,7 +99,7 @@
false,
true,
true,
false
true
]
},
"hash": "7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc"

View File

@@ -99,7 +99,7 @@
false,
true,
true,
false
true
]
},
"hash": "8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1"

View File

@@ -99,7 +99,7 @@
false,
true,
true,
false
true
]
},
"hash": "99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2"

View File

@@ -99,7 +99,7 @@
false,
true,
true,
false
true
]
},
"hash": "bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b"

View File

@@ -448,6 +448,9 @@ pub fn check_env_vars() -> bool {
failed |= check_var::<String>("PAYPAL_WEBHOOK_ID");
failed |= check_var::<String>("PAYPAL_CLIENT_ID");
failed |= check_var::<String>("PAYPAL_CLIENT_SECRET");
failed |= check_var::<String>("PAYPAL_NVP_USERNAME");
failed |= check_var::<String>("PAYPAL_NVP_PASSWORD");
failed |= check_var::<String>("PAYPAL_NVP_SIGNATURE");
failed |= check_var::<String>("HCAPTCHA_SECRET");
@@ -482,9 +485,11 @@ pub fn check_env_vars() -> bool {
failed |= check_var::<String>("STRIPE_API_KEY");
failed |= check_var::<String>("STRIPE_WEBHOOK_SECRET");
failed |= check_var::<u64>("ADITUDE_API_KEY");
failed |= check_var::<String>("ADITUDE_API_KEY");
failed |= check_var::<String>("PYRO_API_KEY");
failed |= check_var::<String>("BREX_API_KEY");
failed
}

View File

@@ -23,7 +23,7 @@ pub struct PayoutsQueue {
payout_options: RwLock<Option<PayoutMethods>>,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
struct PayPalCredentials {
access_token: String,
token_type: String,
@@ -36,6 +36,12 @@ struct PayoutMethods {
expires: DateTime<Utc>,
}
#[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<Option<AccountBalance>, 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<BrexAccount>,
}
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::<BrexResponse>()
.await?;
Ok(Some(AccountBalance {
available: Decimal::from(
res.items
.iter()
.map(|x| x.available_balance.amount)
.sum::<i64>(),
) / Decimal::from(100),
pending: Decimal::from(
res.items
.iter()
.map(|x| {
x.current_balance.amount - x.available_balance.amount
})
.sum::<i64>(),
) / Decimal::from(100),
}))
}
pub async fn get_paypal_balance() -> Result<Option<AccountBalance>, 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(&params).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<Option<AccountBalance>, 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<FundingSource>,
}
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)]

View File

@@ -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<PayoutsQueue>,
) -> Result<HttpResponse, ApiError> {
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,
})))
}