aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuppie2026-09-29 10:19:33 -0600
committerstuppie2026-09-29 10:19:33 -0600
commitca73e3876ee241726ef0162dd78a0c2bf35174ee (patch)
tree0259f8ce3da7277d59b2912ad25909a08843ed1a
parentbb9eded73727d84665836c2ca30dd849d9bb7987 (diff)
downloadgeneralresearch-ca73e3876ee241726ef0162dd78a0c2bf35174ee.tar.gz
generalresearch-ca73e3876ee241726ef0162dd78a0c2bf35174ee.zip
cashout methods : with an optional can_redeem
-rw-r--r--generalresearch/managers/thl/ledger_manager/thl_ledger.py10
-rw-r--r--generalresearch/managers/thl/wallet/user_payout.py19
-rw-r--r--generalresearch/models/thl/wallet/cashout_method.py16
-rw-r--r--tests/managers/thl/test_ledger/test_wallet.py2
4 files changed, 31 insertions, 16 deletions
diff --git a/generalresearch/managers/thl/ledger_manager/thl_ledger.py b/generalresearch/managers/thl/ledger_manager/thl_ledger.py
index de7cae9..cd82d4a 100644
--- a/generalresearch/managers/thl/ledger_manager/thl_ledger.py
+++ b/generalresearch/managers/thl/ledger_manager/thl_ledger.py
@@ -2009,6 +2009,16 @@ class ThlLedgerManager(LedgerManager):
)
return self.get_account_balance(wallet)
+ def get_user_cashout_balance(self, user: User) -> int:
+ """Return the balance currently available for user cashouts."""
+ wallet_balance = self.get_user_wallet_balance(user)
+ balance_type = user.product.user_wallet_config.balance_type
+ if balance_type == "wallet_balance":
+ return wallet_balance
+ if balance_type == "redeemable_balance":
+ return self.get_user_redeemable_wallet_balance(user, wallet_balance)
+ raise ValueError(f"unexpected balance_type={balance_type}")
+
def get_user_redeemable_wallet_balance(
self, user: User, user_wallet_balance: int
) -> PositiveInt:
diff --git a/generalresearch/managers/thl/wallet/user_payout.py b/generalresearch/managers/thl/wallet/user_payout.py
index 432eac9..99f80ef 100644
--- a/generalresearch/managers/thl/wallet/user_payout.py
+++ b/generalresearch/managers/thl/wallet/user_payout.py
@@ -8,7 +8,7 @@ import slack
from pydantic import NonNegativeInt, PositiveInt
from redis import Redis
-from generalresearch.currency import USDCent
+from generalresearch.currency import USDCent, format_usd_cent
from generalresearch.managers.thl.cashout_method import CashoutMethodManager
from generalresearch.managers.thl.ledger_manager.exceptions import (
LedgerTransactionCreateError,
@@ -106,7 +106,7 @@ class UserPayoutEventManager(PayoutEventManager):
product_user_id=pe.user.product_user_id,
amount=USDCent(pe.amount),
payout_type=pe.payout_type,
- created=pe.created
+ created=pe.created,
)
@staticmethod
@@ -430,7 +430,6 @@ class UserPayoutEventManager(PayoutEventManager):
f"Amount must be between 0 and $250.00. Got {amount.to_usd_str()}"
)
- product = user.product
banned_countries = user.product.user_health_config.banned_countries
assert not user_ip_history_manager.is_user_anonymous(user), (
@@ -439,20 +438,10 @@ class UserPayoutEventManager(PayoutEventManager):
if country_iso in banned_countries:
raise AssertionError("Banned country requesting redemption")
- wallet_balance = ledger_manager.get_user_wallet_balance(user)
- if product.user_wallet_config.balance_type == "wallet_balance":
- redeemable_amount = wallet_balance
- elif product.user_wallet_config.balance_type == "redeemable_balance":
- redeemable_amount = ledger_manager.get_user_redeemable_wallet_balance(
- user, wallet_balance
- )
- else:
- raise ValueError(
- f"unexpected balance_type={product.user_wallet_config.balance_type}"
- )
+ redeemable_amount = ledger_manager.get_user_cashout_balance(user)
assert amount <= redeemable_amount, (
- f"User requesting more than their redeemable balance ({amount} > {redeemable_amount})"
+ f"User requesting more than their redeemable balance ({amount.to_usd_str()} > {format_usd_cent(redeemable_amount)})"
)
# Simple dedupe mechanism. Don't allow more than 1 per user_id per minute per cashout_method.
diff --git a/generalresearch/models/thl/wallet/cashout_method.py b/generalresearch/models/thl/wallet/cashout_method.py
index 5502f86..518aa2f 100644
--- a/generalresearch/models/thl/wallet/cashout_method.py
+++ b/generalresearch/models/thl/wallet/cashout_method.py
@@ -195,9 +195,23 @@ class CashoutMethodOut(CashoutMethodBase):
"incrementing values.",
)
+ can_redeem: bool | None = Field(
+ default=None,
+ description="Whether the user currently has enough redeemable wallet balance "
+ "to meet this cashout method's minimum value. None when not evaluated for "
+ "a user.",
+ )
+
@classmethod
- def from_cashout_method(cls, cm: CashoutMethod) -> Self:
+ def from_cashout_method(
+ cls,
+ cm: CashoutMethod,
+ *,
+ cashout_balance: int | None = None,
+ ) -> Self:
d = cm.model_dump()
+ if cashout_balance is not None and cm.min_value_usd is not None:
+ d["can_redeem"] = cashout_balance >= int(cm.min_value_usd)
if cm.user:
d["product_id"] = cm.user.product_id
d["product_user_id"] = cm.user.product_user_id
diff --git a/tests/managers/thl/test_ledger/test_wallet.py b/tests/managers/thl/test_ledger/test_wallet.py
index 0a1da73..f154c19 100644
--- a/tests/managers/thl/test_ledger/test_wallet.py
+++ b/tests/managers/thl/test_ledger/test_wallet.py
@@ -59,6 +59,7 @@ class TestGetUserWalletBalance:
user: User = user_factory(product=schrute_product)
balance = thl_ledger_manager.get_user_wallet_balance(user=user)
assert balance == 0
+ assert thl_ledger_manager.get_user_cashout_balance(user=user) == 0
assert isinstance(user.product, Product)
balance_string = user.product.format_payout_format(Decimal(balance) / 100)
assert balance_string == "0 Schrute Bucks"
@@ -97,3 +98,4 @@ class TestGetUserWalletBalance:
user=user, user_wallet_balance=balance
)
assert redeemable_balance == 20 + 100
+ assert thl_ledger_manager.get_user_cashout_balance(user=user) == 20 + 100