share refund logic

This commit is contained in:
Brandon Thomas committed 2026-03-09 12:39:29 -04:00
1 parent 1b4d3e0f4b
commit f8e400019d
6 files changed
+59 -147

No files matched your search

@@ -0,0 +1 @@
pub mod refund_wallet_after_api_failure;
@@ -0,0 +1,51 @@
use log::{error, info};
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
use sqlx::Acquire;
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
pub async fn refund_wallet_after_api_failure(
ledger_entry_token: &WalletLedgerEntryToken,
connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
) {
let mut transaction = match connection.begin().await {
Ok(tx) => tx,
Err(err) => {
error!(
"Failed to begin refund transaction after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
return;
}
};
match try_to_refund_ledger_entry(ledger_entry_token, &mut transaction).await {
Ok(WalletRefundOutcome::Refunded(summary)) => {
info!(
"Refunded {} credits after API failure (ledger {} → refund ledger {}).",
summary.refund_amount,
ledger_entry_token.as_str(),
summary.refund_ledger_entry_token.as_str(),
);
if let Err(err) = transaction.commit().await {
error!(
"Failed to commit refund after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
}
}
Ok(WalletRefundOutcome::AlreadyRefunded) => {
info!(
"Ledger entry {} was already refunded; no action needed.",
ledger_entry_token.as_str()
);
let _ = transaction.rollback().await;
}
Err(err) => {
error!(
"Failed to refund ledger entry {} after API failure: {:?}",
ledger_entry_token.as_str(), err
);
let _ = transaction.rollback().await;
}
}
}
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -20,7 +21,6 @@ use mysql_queries::queries::generic_inference::worldlabs::insert_generic_inferen
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
@@ -155,7 +155,7 @@ pub async fn generate_worldlabs_marble_0p1_mini_splat_handler(
Ok(result) => result,
Err(err) => {
warn!("World Labs generate_world error: {:?}", err);
refund_after_api_failure(&wallet_deduction.ledger_entry_token, &mut mysql_connection).await;
refund_wallet_after_api_failure(&wallet_deduction.ledger_entry_token, &mut mysql_connection).await;
return Err(CommonWebError::ServerError);
}
};
@@ -247,50 +247,3 @@ pub async fn generate_worldlabs_marble_0p1_mini_splat_handler(
inference_job_token: job_token,
}))
}
async fn refund_after_api_failure(
ledger_entry_token: &tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken,
connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
) {
let mut transaction = match connection.begin().await {
Ok(tx) => tx,
Err(err) => {
error!(
"Failed to begin refund transaction after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
return;
}
};
match try_to_refund_ledger_entry(ledger_entry_token, &mut transaction).await {
Ok(WalletRefundOutcome::Refunded(summary)) => {
info!(
"Refunded {} credits after API failure (ledger {} → refund ledger {}).",
summary.refund_amount,
ledger_entry_token.as_str(),
summary.refund_ledger_entry_token.as_str(),
);
if let Err(err) = transaction.commit().await {
error!(
"Failed to commit refund after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
}
}
Ok(WalletRefundOutcome::AlreadyRefunded) => {
info!(
"Ledger entry {} was already refunded; no action needed.",
ledger_entry_token.as_str()
);
let _ = transaction.rollback().await;
}
Err(err) => {
error!(
"Failed to refund ledger entry {} after API failure: {:?}",
ledger_entry_token.as_str(), err
);
let _ = transaction.rollback().await;
}
}
}
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -20,7 +21,6 @@ use mysql_queries::queries::generic_inference::worldlabs::insert_generic_inferen
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use world_labs_api::api::api_types::world_labs_model::WorldLabsModel;
@@ -155,7 +155,7 @@ pub async fn generate_worldlabs_marble_0p1_plus_splat_handler(
Ok(result) => result,
Err(err) => {
warn!("World Labs generate_world error: {:?}", err);
refund_after_api_failure(&wallet_deduction.ledger_entry_token, &mut mysql_connection).await;
refund_wallet_after_api_failure(&wallet_deduction.ledger_entry_token, &mut mysql_connection).await;
return Err(CommonWebError::ServerError);
}
};
@@ -247,50 +247,3 @@ pub async fn generate_worldlabs_marble_0p1_plus_splat_handler(
inference_job_token: job_token,
}))
}
async fn refund_after_api_failure(
ledger_entry_token: &tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken,
connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
) {
let mut transaction = match connection.begin().await {
Ok(tx) => tx,
Err(err) => {
error!(
"Failed to begin refund transaction after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
return;
}
};
match try_to_refund_ledger_entry(ledger_entry_token, &mut transaction).await {
Ok(WalletRefundOutcome::Refunded(summary)) => {
info!(
"Refunded {} credits after API failure (ledger {} → refund ledger {}).",
summary.refund_amount,
ledger_entry_token.as_str(),
summary.refund_ledger_entry_token.as_str(),
);
if let Err(err) = transaction.commit().await {
error!(
"Failed to commit refund after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
}
}
Ok(WalletRefundOutcome::AlreadyRefunded) => {
info!(
"Ledger entry {} was already refunded; no action needed.",
ledger_entry_token.as_str()
);
let _ = transaction.rollback().await;
}
Err(err) => {
error!(
"Failed to refund ledger entry {} after API failure: {:?}",
ledger_entry_token.as_str(), err
);
let _ = transaction.rollback().await;
}
}
}
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoint_helpers::refund_wallet_after_api_failure::refund_wallet_after_api_failure;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -29,7 +30,6 @@ use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_it
insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem,
};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::wallets::refund::try_to_refund_ledger_entry::{try_to_refund_ledger_entry, WalletRefundOutcome};
use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
use seedance2pro::requests::generate_video::generate_video::{
generate_video, BatchCount, GenerateVideoArgs, Resolution,
@@ -221,7 +221,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
Ok(resp) => resp,
Err(err) => {
warn!("Error calling seedance2pro generate_video: {:?}", err);
refund_after_api_failure(&deduction_result.ledger_entry_token, &mut mysql_connection).await;
refund_wallet_after_api_failure(&deduction_result.ledger_entry_token, &mut mysql_connection).await;
// TODO: We need to start returning robust failure messages to the caller.
return Err(CommonWebError::ServerError);
@@ -375,53 +375,6 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
}))
}
async fn refund_after_api_failure(
ledger_entry_token: &tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken,
connection: &mut sqlx::pool::PoolConnection<sqlx::MySql>,
) {
let mut transaction = match connection.begin().await {
Ok(tx) => tx,
Err(err) => {
error!(
"Failed to begin refund transaction after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
return;
}
};
match try_to_refund_ledger_entry(ledger_entry_token, &mut transaction).await {
Ok(WalletRefundOutcome::Refunded(summary)) => {
info!(
"Refunded {} credits after API failure (ledger {} → refund ledger {}).",
summary.refund_amount,
ledger_entry_token.as_str(),
summary.refund_ledger_entry_token.as_str(),
);
if let Err(err) = transaction.commit().await {
error!(
"Failed to commit refund after API failure (ledger {}): {:?}",
ledger_entry_token.as_str(), err
);
}
}
Ok(WalletRefundOutcome::AlreadyRefunded) => {
info!(
"Ledger entry {} was already refunded; no action needed.",
ledger_entry_token.as_str()
);
let _ = transaction.rollback().await;
}
Err(err) => {
error!(
"Failed to refund ledger entry {} after API failure: {:?}",
ledger_entry_token.as_str(), err
);
let _ = transaction.rollback().await;
}
}
}
async fn upload_to_seedance2pro(
session: &Seedance2ProSession,
our_cdn_url: &str,
@@ -1,5 +1,6 @@
pub mod common_requests;
pub mod common_responses;
pub mod endpoint_helpers;
pub mod cookies;
pub mod deprecated_endpoints;
pub mod endpoints;