forked from didirus/AstralRinth
Tax fixes (#4435)
* Only update the PaymentMethod ID if not using placeholder ID * comment * Create Anrok transactions for all charges * Fix comment * Prefer using payment method's address rather than customer address * chore: query cache, clippy, fmt * Retrieve stripe address from PM * chore: query cache, clippy, fmt * fmt * bring back the query cache * Better address retrieval for updating tax amounts, always update tax_last_updated * chore: query cache, clippy, fmt * Don't set PM in ctoken interactive session for new PIs
This commit is contained in:
committed by
GitHub
parent
d418eaee12
commit
b4eba5a0d5
22
apps/labrinth/.sqlx/query-263c6c447ee7070e0b503ffe3e98a7edd9a97a125aee3142904bb0a057a82d98.json
generated
Normal file
22
apps/labrinth/.sqlx/query-263c6c447ee7070e0b503ffe3e98a7edd9a97a125aee3142904bb0a057a82d98.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM organizations o\n JOIN teams t ON o.team_id = t.id\n JOIN team_members tm ON t.id = tm.team_id\n WHERE tm.user_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "263c6c447ee7070e0b503ffe3e98a7edd9a97a125aee3142904bb0a057a82d98"
|
||||
}
|
||||
34
apps/labrinth/.sqlx/query-2900514e9ff89519201700d3a96848690959f9ef946c8e7cf730e03cb92563b0.json
generated
Normal file
34
apps/labrinth/.sqlx/query-2900514e9ff89519201700d3a96848690959f9ef946c8e7cf730e03cb92563b0.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT projects, organizations, collections\n FROM user_limits\n WHERE user_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "projects",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "organizations",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "collections",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2900514e9ff89519201700d3a96848690959f9ef946c8e7cf730e03cb92563b0"
|
||||
}
|
||||
32
apps/labrinth/.sqlx/query-382e4cceddc6b51f925e467b1e3d976d837777e31abee642df80cb1460ac0845.json
generated
Normal file
32
apps/labrinth/.sqlx/query-382e4cceddc6b51f925e467b1e3d976d837777e31abee642df80cb1460ac0845.json
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT projects, organizations, collections\n FROM user_limits\n WHERE user_id IS NULL",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "projects",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "organizations",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "collections",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "382e4cceddc6b51f925e467b1e3d976d837777e31abee642df80cb1460ac0845"
|
||||
}
|
||||
17
apps/labrinth/.sqlx/query-5184ad30a3a276892248037d43de23dbc90c968f4342ec108e65fe3c455466d3.json
generated
Normal file
17
apps/labrinth/.sqlx/query-5184ad30a3a276892248037d43de23dbc90c968f4342ec108e65fe3c455466d3.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO user_limits (user_id, projects, organizations, collections)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (user_id) DO UPDATE\n SET projects = EXCLUDED.projects,\n organizations = EXCLUDED.organizations,\n collections = EXCLUDED.collections",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5184ad30a3a276892248037d43de23dbc90c968f4342ec108e65fe3c455466d3"
|
||||
}
|
||||
22
apps/labrinth/.sqlx/query-c0d0cd9a1a80e6aa2f82decd9b46eb5a74e238e352fe7745f98ce264074adde5.json
generated
Normal file
22
apps/labrinth/.sqlx/query-c0d0cd9a1a80e6aa2f82decd9b46eb5a74e238e352fe7745f98ce264074adde5.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM collections\n WHERE user_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c0d0cd9a1a80e6aa2f82decd9b46eb5a74e238e352fe7745f98ce264074adde5"
|
||||
}
|
||||
22
apps/labrinth/.sqlx/query-f7d3a57e0a0bc81392d712a08944cf929ee73f620507ac868b28afc08d8ed6f5.json
generated
Normal file
22
apps/labrinth/.sqlx/query-f7d3a57e0a0bc81392d712a08944cf929ee73f620507ac868b28afc08d8ed6f5.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) FROM mods m\n JOIN teams t ON m.team_id = t.id\n JOIN team_members tm ON t.id = tm.team_id\n WHERE tm.user_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f7d3a57e0a0bc81392d712a08944cf929ee73f620507ac868b28afc08d8ed6f5"
|
||||
}
|
||||
@@ -24,6 +24,7 @@ use crate::util::archon::ArchonClient;
|
||||
use crate::util::archon::{CreateServerRequest, Specs};
|
||||
use ariadne::ids::base62_impl::to_base62;
|
||||
use chrono::Utc;
|
||||
use futures::FutureExt;
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashSet;
|
||||
@@ -115,7 +116,6 @@ pub async fn index_subscriptions(
|
||||
let redis_ref = redis.clone();
|
||||
|
||||
struct ProcessedCharge {
|
||||
charge: DBCharge,
|
||||
new_tax_amount: i64,
|
||||
product_name: String,
|
||||
}
|
||||
@@ -128,7 +128,9 @@ pub async fn index_subscriptions(
|
||||
let pg = pg_ref.clone();
|
||||
let redis = redis_ref.clone();
|
||||
|
||||
async move {
|
||||
let charge_clone = charge.clone();
|
||||
|
||||
let op_fut = async move {
|
||||
let tax_id = DBProductsTaxIdentifier::get_price(
|
||||
charge.price_id,
|
||||
&pg,
|
||||
@@ -148,38 +150,6 @@ pub async fn index_subscriptions(
|
||||
})?;
|
||||
|
||||
let stripe_address = 'a: {
|
||||
let stripe_id: stripe::PaymentIntentId = charge
|
||||
.payment_platform_id
|
||||
.as_ref()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"Charge has no payment platform ID"
|
||||
.to_owned(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Attempt retrieving the address via the payment intent's payment method
|
||||
|
||||
let pi = stripe::PaymentIntent::retrieve(
|
||||
&stripe_client,
|
||||
&stripe_id,
|
||||
&["payment_method"],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let pi_stripe_address = pi
|
||||
.payment_method
|
||||
.and_then(|x| x.into_object())
|
||||
.and_then(|x| x.billing_details.address);
|
||||
|
||||
match pi_stripe_address {
|
||||
Some(address) => break 'a address,
|
||||
None => {
|
||||
warn!("PaymentMethod had no address");
|
||||
}
|
||||
};
|
||||
|
||||
let stripe_customer_id =
|
||||
DBUser::get_id(charge.user_id, &pg, &redis)
|
||||
.await?
|
||||
@@ -197,23 +167,48 @@ pub async fn index_subscriptions(
|
||||
)
|
||||
},
|
||||
)
|
||||
})?
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
ApiError::InvalidInput(
|
||||
"User Stripe customer ID was invalid".to_owned(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let customer_id = stripe_customer_id.parse().map_err(|e| ApiError::InvalidInput(format!("Charge's Stripe customer ID was invalid ({e})")))?;
|
||||
|
||||
let customer = stripe::Customer::retrieve(
|
||||
&stripe_client,
|
||||
&customer_id,
|
||||
&[],
|
||||
&stripe_customer_id,
|
||||
&["invoice_settings.default_payment_method"],
|
||||
)
|
||||
.await?;
|
||||
|
||||
customer.address.ok_or_else(|| {
|
||||
let payment_method = customer
|
||||
.invoice_settings
|
||||
.and_then(|x| {
|
||||
x.default_payment_method.and_then(|x| x.into_object())
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"Stripe customer had no address"
|
||||
.to_owned(),
|
||||
"Customer has no default payment method!".to_string(),
|
||||
)
|
||||
})?
|
||||
})?;
|
||||
|
||||
let stripe_address = payment_method.billing_details.address;
|
||||
|
||||
// Attempt the default payment method's address first, then the customer's address.
|
||||
match stripe_address {
|
||||
Some(address) => break 'a address,
|
||||
None => {
|
||||
warn!("PaymentMethod had no address");
|
||||
}
|
||||
};
|
||||
|
||||
customer.address.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"Couldn't get an address for the Stripe customer"
|
||||
.to_owned(),
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
let customer_address =
|
||||
@@ -238,28 +233,29 @@ pub async fn index_subscriptions(
|
||||
|
||||
Result::<ProcessedCharge, ApiError>::Ok(
|
||||
ProcessedCharge {
|
||||
charge,
|
||||
new_tax_amount: tax_amount,
|
||||
product_name: product
|
||||
.name
|
||||
.unwrap_or_else(|| "Modrinth".to_owned()),
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
op_fut.then(move |res| async move { (charge_clone, res) })
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
while let Some(result) = futures.next().await {
|
||||
processed_charges += 1;
|
||||
|
||||
match result {
|
||||
Ok(ProcessedCharge {
|
||||
let mut charge = match result {
|
||||
(
|
||||
mut charge,
|
||||
new_tax_amount,
|
||||
product_name,
|
||||
}) => {
|
||||
charge.tax_last_updated = Some(Utc::now());
|
||||
|
||||
Ok(ProcessedCharge {
|
||||
new_tax_amount,
|
||||
product_name,
|
||||
}),
|
||||
) => {
|
||||
if new_tax_amount != charge.tax_amount {
|
||||
// The price of the subscription has changed, we need to insert a notification
|
||||
// for this.
|
||||
@@ -293,12 +289,16 @@ pub async fn index_subscriptions(
|
||||
charge.tax_amount = new_tax_amount;
|
||||
}
|
||||
|
||||
charge.upsert(&mut txn).await?;
|
||||
charge
|
||||
}
|
||||
Err(error) => {
|
||||
(charge, Err(error)) => {
|
||||
error!(%error, "Error indexing tax amount on charge");
|
||||
charge
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
charge.tax_last_updated = Some(Utc::now());
|
||||
charge.upsert(&mut txn).await?;
|
||||
}
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
@@ -575,7 +575,12 @@ pub async fn create_or_update_payment_intent(
|
||||
intent.customer = Some(customer_id);
|
||||
intent.metadata = Some(metadata);
|
||||
intent.receipt_email = user.email.as_deref();
|
||||
intent.payment_method = Some(payment_method.id.clone());
|
||||
if let PaymentSession::Interactive {
|
||||
payment_request_type: PaymentRequestType::PaymentMethod { .. },
|
||||
} = &payment_session
|
||||
{
|
||||
intent.payment_method = Some(payment_method.id.clone());
|
||||
}
|
||||
|
||||
payment_session.set_payment_intent_session_options(&mut intent);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user