forked from didirus/AstralRinth
Billing fixes (#4422)
* 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
This commit is contained in:
committed by
GitHub
parent
d43451e398
commit
14af3d0763
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n charges.id, charges.user_id, charges.price_id, charges.amount, charges.currency_code, charges.status, charges.due, charges.last_attempt,\n charges.charge_type, charges.subscription_id, charges.tax_amount, charges.tax_platform_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n charges.subscription_interval AS \"subscription_interval?\",\n charges.payment_platform,\n charges.payment_platform_id AS \"payment_platform_id?\",\n charges.parent_charge_id AS \"parent_charge_id?\",\n charges.net AS \"net?\",\n\t\t\t\tcharges.tax_last_updated AS \"tax_last_updated?\",\n\t\t\t\tcharges.tax_drift_loss AS \"tax_drift_loss?\"\n FROM charges\n \n\t\t\tWHERE\n\t\t\t status = 'succeeded'\n\t\t\t AND tax_platform_id IS NULL\n\t\t\t AND tax_amount <> 0\n\t\t\tORDER BY due ASC\n\t\t\tFOR NO KEY UPDATE SKIP LOCKED\n\t\t\tLIMIT $1\n\t\t\t",
|
"query": "\n SELECT\n charges.id, charges.user_id, charges.price_id, charges.amount, charges.currency_code, charges.status, charges.due, charges.last_attempt,\n charges.charge_type, charges.subscription_id, charges.tax_amount, charges.tax_platform_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n charges.subscription_interval AS \"subscription_interval?\",\n charges.payment_platform,\n charges.payment_platform_id AS \"payment_platform_id?\",\n charges.parent_charge_id AS \"parent_charge_id?\",\n charges.net AS \"net?\",\n\t\t\t\tcharges.tax_last_updated AS \"tax_last_updated?\",\n\t\t\t\tcharges.tax_drift_loss AS \"tax_drift_loss?\"\n FROM charges\n \n\t\t\tWHERE\n\t\t\t status = 'succeeded'\n\t\t\t AND tax_platform_id IS NULL\n\t\t\tORDER BY due ASC\n\t\t\tFOR NO KEY UPDATE SKIP LOCKED\n\t\t\tLIMIT $1\n\t\t\t",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -126,5 +126,5 @@
|
|||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "a20149894f03ff65c35f4ce9c2c6e8735867376783d8665036cfc85df3f4867d"
|
"hash": "8b4979c9f0eb427d5f8a0e4c4e89eb8127f1cb3cd07f89fc2d206e3b5c8805f6"
|
||||||
}
|
}
|
||||||
@@ -336,8 +336,7 @@ impl DBCharge {
|
|||||||
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all charges which are missing a tax identifier, that is, are 1. succeeded, 2. have a tax amount and
|
/// Returns all charges which are missing a tax identifier, that is, are succeeded and haven't been assigned a tax identifier yet.
|
||||||
/// 3. haven't been assigned a tax identifier yet.
|
|
||||||
///
|
///
|
||||||
/// Charges are locked.
|
/// Charges are locked.
|
||||||
pub async fn get_missing_tax_identifier_lock(
|
pub async fn get_missing_tax_identifier_lock(
|
||||||
@@ -349,7 +348,6 @@ impl DBCharge {
|
|||||||
WHERE
|
WHERE
|
||||||
status = 'succeeded'
|
status = 'succeeded'
|
||||||
AND tax_platform_id IS NULL
|
AND tax_platform_id IS NULL
|
||||||
AND tax_amount <> 0
|
|
||||||
ORDER BY due ASC
|
ORDER BY due ASC
|
||||||
FOR NO KEY UPDATE SKIP LOCKED
|
FOR NO KEY UPDATE SKIP LOCKED
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
|
|||||||
@@ -129,23 +129,6 @@ pub async fn index_subscriptions(
|
|||||||
let redis = redis_ref.clone();
|
let redis = redis_ref.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let stripe_customer_id =
|
|
||||||
DBUser::get_id(charge.user_id, &pg, &redis)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::from(DatabaseError::Database(
|
|
||||||
sqlx::Error::RowNotFound,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.and_then(|user| {
|
|
||||||
user.stripe_customer_id.ok_or_else(|| {
|
|
||||||
ApiError::InvalidInput(
|
|
||||||
"User has no Stripe customer ID"
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tax_id = DBProductsTaxIdentifier::get_price(
|
let tax_id = DBProductsTaxIdentifier::get_price(
|
||||||
charge.price_id,
|
charge.price_id,
|
||||||
&pg,
|
&pg,
|
||||||
@@ -164,26 +147,81 @@ pub async fn index_subscriptions(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let Ok(customer_id): Result<stripe::CustomerId, _> =
|
let stripe_address = 'a: {
|
||||||
stripe_customer_id.parse()
|
let stripe_id: stripe::PaymentIntentId = charge
|
||||||
else {
|
.payment_platform_id
|
||||||
return Err(ApiError::InvalidInput(
|
.as_ref()
|
||||||
"Charge's Stripe customer ID was invalid"
|
.and_then(|x| x.parse().ok())
|
||||||
.to_owned(),
|
.ok_or_else(|| {
|
||||||
));
|
ApiError::InvalidInput(
|
||||||
};
|
"Charge has no payment platform ID"
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let customer = stripe::Customer::retrieve(
|
// Attempt retrieving the address via the payment intent's payment method
|
||||||
&stripe_client,
|
|
||||||
&customer_id,
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(stripe_address) = customer.address else {
|
let pi = stripe::PaymentIntent::retrieve(
|
||||||
return Err(ApiError::InvalidInput(
|
&stripe_client,
|
||||||
"Stripe customer had no address".to_owned(),
|
&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?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::from(DatabaseError::Database(
|
||||||
|
sqlx::Error::RowNotFound,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.and_then(|user| {
|
||||||
|
user.stripe_customer_id.ok_or_else(
|
||||||
|
|| {
|
||||||
|
ApiError::InvalidInput(
|
||||||
|
"User has no Stripe customer ID"
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let Ok(customer_id): Result<stripe::CustomerId, _> =
|
||||||
|
stripe_customer_id.parse()
|
||||||
|
else {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Charge's Stripe customer ID was invalid"
|
||||||
|
.to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let customer = stripe::Customer::retrieve(
|
||||||
|
&stripe_client,
|
||||||
|
&customer_id,
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(stripe_address) = customer.address else {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Stripe customer had no address".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
stripe_address
|
||||||
};
|
};
|
||||||
|
|
||||||
let customer_address =
|
let customer_address =
|
||||||
|
|||||||
@@ -530,15 +530,28 @@ pub async fn create_or_update_payment_intent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payment_intent_id) = existing_payment_intent {
|
if let Some(payment_intent_id) = existing_payment_intent {
|
||||||
let update_payment_intent = stripe::UpdatePaymentIntent {
|
let mut update_payment_intent = stripe::UpdatePaymentIntent {
|
||||||
amount: Some(charge_data.amount + tax_amount),
|
amount: Some(charge_data.amount + tax_amount),
|
||||||
currency: Some(inferred_stripe_currency),
|
currency: Some(inferred_stripe_currency),
|
||||||
customer: Some(customer_id),
|
customer: Some(customer_id),
|
||||||
metadata: Some(metadata),
|
metadata: Some(metadata),
|
||||||
payment_method: Some(payment_method.id.clone()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If the payment request type was done through a confirmation token,
|
||||||
|
// the payment method ID is an invalid placeholder so we don't want
|
||||||
|
// to use it.
|
||||||
|
//
|
||||||
|
// The PaymentIntent will be confirmed using the confirmation token
|
||||||
|
// by the client.
|
||||||
|
if let PaymentSession::Interactive {
|
||||||
|
payment_request_type: PaymentRequestType::PaymentMethod { .. },
|
||||||
|
} = &payment_session
|
||||||
|
{
|
||||||
|
update_payment_intent.payment_method =
|
||||||
|
Some(payment_method.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
stripe::PaymentIntent::update(
|
stripe::PaymentIntent::update(
|
||||||
stripe_client,
|
stripe_client,
|
||||||
&payment_intent_id,
|
&payment_intent_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user