diff --git a/app/models/wallet.rb b/app/models/wallet.rb index d6b45008dfd..376d0a4ecc8 100644 --- a/app/models/wallet.rb +++ b/app/models/wallet.rb @@ -57,6 +57,7 @@ def currency # invoice_requires_successful_payment :boolean default(FALSE), not null # last_balance_sync_at :datetime # last_consumed_credit_at :datetime +# lock_version :integer default(0), not null # name :string # ongoing_balance_cents :bigint default(0), not null # ongoing_usage_balance_cents :bigint default(0), not null diff --git a/app/services/wallets/balance/increase_service.rb b/app/services/wallets/balance/increase_service.rb index 87a1fdb81b5..86d135ecc16 100644 --- a/app/services/wallets/balance/increase_service.rb +++ b/app/services/wallets/balance/increase_service.rb @@ -3,34 +3,51 @@ module Wallets module Balance class IncreaseService < BaseService + MAX_RETRIES = 5 + def initialize(wallet:, credits_amount:, reset_consumed_credits: false) super @wallet = wallet @credits_amount = credits_amount @reset_consumed_credits = reset_consumed_credits + @retries = 0 end def call - currency = wallet.balance.currency - amount_cents = wallet.rate_amount * credits_amount * currency.subunit_to_unit - - update_params = { - balance_cents: wallet.balance_cents + amount_cents, - credits_balance: wallet.credits_balance + credits_amount, - last_balance_sync_at: Time.current - } - - if reset_consumed_credits - update_params[:consumed_credits] = [0.0, wallet.consumed_credits - credits_amount].max - update_params[:consumed_amount_cents] = [0, wallet.consumed_amount_cents - amount_cents].max + ActiveRecord::Base.transaction do + currency = wallet.balance.currency + amount_cents = wallet.rate_amount * credits_amount * currency.subunit_to_unit + + update_params = { + balance_cents: wallet.balance_cents + amount_cents, + credits_balance: wallet.credits_balance + credits_amount, + last_balance_sync_at: Time.current + } + + if reset_consumed_credits + update_params[:consumed_credits] = [0.0, wallet.consumed_credits - credits_amount].max + update_params[:consumed_amount_cents] = [0, wallet.consumed_amount_cents - amount_cents].max + end + + wallet.update!(update_params) + Wallets::Balance::RefreshOngoingService.call(wallet:) end - wallet.update!(update_params) - Wallets::Balance::RefreshOngoingService.call(wallet:) - result.wallet = wallet result + rescue ActiveRecord::StaleObjectError + @retries += 1 + + if @retries <= MAX_RETRIES + sleep(0.5) + + wallet.reload + + retry + end + + result.service_failure!(code: 'race_condition_error', message: '') end private diff --git a/db/migrate/20241008080209_add_lock_version_to_wallets.rb b/db/migrate/20241008080209_add_lock_version_to_wallets.rb new file mode 100644 index 00000000000..368fbc84983 --- /dev/null +++ b/db/migrate/20241008080209_add_lock_version_to_wallets.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddLockVersionToWallets < ActiveRecord::Migration[7.1] + def change + add_column :wallets, :lock_version, :integer, default: 0, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index d8baa37660f..75244a8931a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_10_01_112117) do +ActiveRecord::Schema[7.1].define(version: 2024_10_08_080209) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -1160,6 +1160,7 @@ t.decimal "credits_ongoing_usage_balance", precision: 30, scale: 5, default: "0.0", null: false t.boolean "depleted_ongoing_balance", default: false, null: false t.boolean "invoice_requires_successful_payment", default: false, null: false + t.integer "lock_version", default: 0, null: false t.index ["customer_id"], name: "index_wallets_on_customer_id" end