aboutsummaryrefslogtreecommitdiff
path: root/tests/managers/thl/test_wallet/test_user_payout.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/managers/thl/test_wallet/test_user_payout.py')
-rw-r--r--tests/managers/thl/test_wallet/test_user_payout.py435
1 files changed, 435 insertions, 0 deletions
diff --git a/tests/managers/thl/test_wallet/test_user_payout.py b/tests/managers/thl/test_wallet/test_user_payout.py
new file mode 100644
index 0000000..bbc97a3
--- /dev/null
+++ b/tests/managers/thl/test_wallet/test_user_payout.py
@@ -0,0 +1,435 @@
+from datetime import datetime
+from decimal import Decimal
+from random import randint
+from unittest.mock import Mock
+from uuid import uuid4
+
+import pytest
+from faker import Faker
+
+from generalresearch.currency import USDCent
+from generalresearch.managers.thl.cashout_method import CashoutMethodManager
+from generalresearch.managers.thl.ipinfo import GeoIpInfoManager
+from generalresearch.managers.thl.ledger_manager.thl_ledger import ThlLedgerManager
+from generalresearch.managers.thl.userhealth import UserIpHistoryManager
+from generalresearch.managers.thl.wallet.tango import TangoManager
+from generalresearch.managers.thl.wallet.user_payout import (
+ UserPayoutEventManager,
+ make_request_paypal,
+)
+from generalresearch.models.thl.definitions import PayoutStatus
+from generalresearch.models.thl.payout import UserPayoutEvent
+from generalresearch.models.thl.user import User
+from generalresearch.models.thl.wallet.cashout_method import (
+ CashMailCashoutMethodData,
+ CashMailCashoutMethodRequestData,
+ CashoutRequestInfo,
+ PaypalCashoutMethodData,
+ PaypalCashoutMethodRequestData,
+ USDeliveryAddress,
+)
+from generalresearch.models.thl.wallet.definitions import PayoutType
+
+fake = Faker()
+
+
+class TestUserPayoutEventManager:
+ def test_get_by_uuid_and_create(
+ self,
+ user: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ thl_ledger_manager: ThlLedgerManager,
+ utc_now: datetime,
+ ):
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+ request_data = PaypalCashoutMethodRequestData.model_validate(
+ {
+ "email": fake.email(),
+ "interface": "api",
+ }
+ )
+ pe1: UserPayoutEvent = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ payout_type=PayoutType.PAYPAL,
+ cashout_method_uuid=uuid4().hex,
+ amount=100,
+ created=utc_now,
+ request_data=request_data.model_dump(mode="json"),
+ )
+ # these get added by the query
+ pe1.account_reference_type = "user"
+ pe1.account_reference_uuid = user.uuid
+
+ pe2 = user_payout_event_manager.get_by_uuid(pe_uuid=pe1.uuid)
+
+ assert pe1 == pe2
+
+ def test_get_payout_detail_cash_in_mail(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ):
+ user = user_with_wallet
+ data = CashMailCashoutMethodData(
+ delivery_address=USDeliveryAddress.model_validate(
+ {
+ "name_or_attn": "Josh Ackerman",
+ "address": "123 Fake St",
+ "city": "San Francisco",
+ "state": "CA",
+ "postal_code": "12345",
+ }
+ )
+ )
+ cm = cashout_method_manager.create_cash_in_mail_cashout_method(
+ data=data, user=user
+ )
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+
+ rand_amount = randint(a=99, b=999)
+ request_data = CashMailCashoutMethodRequestData.model_validate(
+ cm.data.model_dump()
+ )
+
+ pe = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ cashout_method_uuid=cm.id,
+ amount=rand_amount,
+ ext_ref_id=uuid4().hex,
+ payout_type=PayoutType.CASH_IN_MAIL,
+ request_data=request_data.model_dump(mode="json"),
+ )
+
+ res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
+ assert isinstance(res, CashoutRequestInfo)
+
+ def test_get_payout_detail_paypal(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ ):
+ user = user_with_wallet
+ data = PaypalCashoutMethodData(email=fake.email())
+ cm = cashout_method_manager.create_paypal_cashout_method(data=data, user=user)
+ user_account = thl_ledger_manager.get_account_or_create_user_wallet(user=user)
+
+ rand_amount = randint(a=99, b=999)
+ request_data = make_request_paypal(cm)
+ pe = user_payout_event_manager.create(
+ debit_account_uuid=user_account.uuid,
+ cashout_method_uuid=cm.id,
+ amount=rand_amount,
+ ext_ref_id=uuid4().hex,
+ payout_type=PayoutType.PAYPAL,
+ request_data=request_data.model_dump(mode="json"),
+ )
+
+ res = user_payout_event_manager.get_payout_detail(pe_uuid=pe.uuid)
+ assert isinstance(res, CashoutRequestInfo)
+
+
+class TestUserRequestRedeem:
+ @staticmethod
+ def request_paypal_payout(
+ *,
+ user: User,
+ amount: USDCent,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ip: str = "8.8.8.8",
+ fund_amount: Decimal = Decimal("10.00"),
+ ) -> UserPayoutEvent:
+ email = fake.email()
+ cashout_method = cashout_method_manager.create_paypal_cashout_method(
+ data=PaypalCashoutMethodData(email=email),
+ user=user,
+ )
+ ip_record_factory(user=user, ip=ip)
+ thl_ledger_manager.create_tx_user_bonus(
+ user=user,
+ amount=fund_amount,
+ ref_uuid=uuid4().hex,
+ description="Fund PayPal redemption test",
+ )
+
+ tango_manager = Mock(spec=TangoManager)
+ tango_manager.get_exchange_rates.return_value = {}
+
+ return user_payout_event_manager.user_request_redeem(
+ user=user,
+ cashout_method_id=cashout_method.id,
+ amount=amount,
+ tango_manager=tango_manager,
+ cashout_method_manager=cashout_method_manager,
+ ledger_manager=thl_ledger_manager,
+ user_ip_history_manager=user_iphistory_manager,
+ redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ geoip_info_manager: GeoIpInfoManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ user = user_with_wallet
+ payout = self.request_paypal_payout(
+ user=user,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ assert payout.status == PayoutStatus.PENDING
+ assert payout.payout_type == PayoutType.PAYPAL
+ assert payout.amount == 600
+ assert payout.request_data["interface"] == "api"
+ assert thl_ledger_manager.get_user_wallet_balance(user) == 400
+ assert user_payout_event_manager.get_by_uuid(payout.uuid) == payout
+
+ detail = user_payout_event_manager.get_payout_detail(payout.uuid)
+ assert not detail.transaction_info
+ assert detail.description == "PayPal"
+
+ def test_user_request_redeem_paypal_complete(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ geoip_info_manager: GeoIpInfoManager,
+ ip_record_factory,
+ thl_redis_client,
+ create_main_accounts,
+ ):
+ create_main_accounts()
+ user = user_with_wallet
+ payout = self.request_paypal_payout(
+ user=user,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.APPROVED,
+ ext_ref_id="paypal-batch-id",
+ )
+ thl_ledger_manager.create_tx_user_payout_complete(
+ user=user,
+ payout_event=payout,
+ fee_amount=Decimal("0.25"),
+ )
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.COMPLETE,
+ order_data={"transaction_id": "paypal-transaction-id"},
+ )
+
+ detail = user_payout_event_manager.get_payout_detail(pe_uuid=payout.uuid)
+ assert detail.status == PayoutStatus.COMPLETE
+ assert detail.transaction_info == {"transaction_id": "paypal-transaction-id"}
+
+ assert thl_ledger_manager.get_user_wallet_balance(user_with_wallet) == 400
+ bp_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid(
+ product_uuid=user.to_user_ref().product_id, expense_name="paypal"
+ )
+ assert thl_ledger_manager.get_account_balance(bp_expense) == -25
+
+ def test_user_request_redeem_tango_complete(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ create_main_accounts,
+ setup_cashoutmethod_db,
+ example_tango_cashout_methods,
+ ):
+ create_main_accounts()
+ setup_cashoutmethod_db()
+ user = user_with_wallet
+ # This is the italian Amazon.it gift card
+ cashout_method = example_tango_cashout_methods[1]
+ # This is an IP that we have hard-coded by the geoip_info_manager as Italy
+ ip_record_factory(user=user, ip="2.2.2.2")
+
+ thl_ledger_manager.create_tx_user_bonus(
+ user=user,
+ amount=Decimal("5.00"),
+ ref_uuid=uuid4().hex,
+ description="Fund Tango redemption test",
+ )
+
+ tango_client = Mock()
+ tango_client.get_exchange_rates.return_value = {
+ "exchangeRates": [
+ {"rewardCurrency": "USD", "baseCurrency": "EUR", "baseFx": 1.14792}
+ ]
+ }
+ tango_manager = TangoManager(
+ tango_client=tango_client,
+ tango_account_id="test-account",
+ tango_customer_id="test-customer",
+ cashout_method_manager=cashout_method_manager,
+ )
+ request = tango_manager.make_request(
+ amount=USDCent(2_00),
+ cashout_method=cashout_method,
+ payout_event_id=uuid4().hex,
+ )
+ assert request.amount == Decimal(2.0 / 1.14792).quantize(
+ Decimal("0.01")
+ ) # ~ 1.74 EUR
+
+ payout = user_payout_event_manager.user_request_redeem(
+ user=user,
+ cashout_method_id=cashout_method.id,
+ amount=USDCent(2_00),
+ tango_manager=tango_manager,
+ cashout_method_manager=cashout_method_manager,
+ ledger_manager=thl_ledger_manager,
+ user_ip_history_manager=user_iphistory_manager,
+ redis_client=thl_redis_client,
+ )
+ assert payout.request_data["amount"] == '1.74'
+ assert payout.request_data["externalRefID"] == payout.uuid
+
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.APPROVED,
+ ext_ref_id="tango-order-id",
+ )
+ thl_ledger_manager.create_tx_user_payout_complete(
+ user=user,
+ payout_event=payout,
+ )
+ user_payout_event_manager.update(
+ payout_event=payout,
+ status=PayoutStatus.COMPLETE,
+ order_data={
+ "reward": {
+ "credentialList": [
+ {
+ "credentialType": "giftCardCode",
+ "credentialValue": "TEST-CODE",
+ }
+ ],
+ "redemptionInstructions": "Use this code at checkout.",
+ }
+ },
+ )
+
+ detail = user_payout_event_manager.get_payout_detail(pe_uuid=payout.uuid)
+ assert payout.payout_type == PayoutType.TANGO
+ assert detail.status == PayoutStatus.COMPLETE
+ assert detail.transaction_info == {
+ "credential_list": [
+ {
+ "credentialType": "giftCardCode",
+ "credentialValue": "TEST-CODE",
+ }
+ ],
+ "redemption_instructions": "Use this code at checkout.",
+ }
+ assert thl_ledger_manager.get_user_wallet_balance(user) == 300
+ bp_expense = thl_ledger_manager.get_account_or_create_bp_expense_by_uuid(
+ product_uuid=user.to_user_ref().product_id,
+ expense_name="tango",
+ )
+ assert thl_ledger_manager.get_account_balance(bp_expense) == -7
+
+ def test_user_request_redeem_paypal_insufficient_balance(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ with pytest.raises(AssertionError, match="more than their redeemable balance"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(1_100),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal_blocked_user(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ user_with_wallet.blocked = True
+
+ with pytest.raises(AssertionError, match="Blocked user"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ )
+
+ def test_user_request_redeem_paypal_anonymous_user(
+ self,
+ user_with_wallet: User,
+ user_payout_event_manager: UserPayoutEventManager,
+ cashout_method_manager: CashoutMethodManager,
+ thl_ledger_manager: ThlLedgerManager,
+ user_iphistory_manager: UserIpHistoryManager,
+ ip_record_factory,
+ thl_redis_client,
+ ):
+ with pytest.raises(AssertionError, match="Anonymous user"):
+ self.request_paypal_payout(
+ user=user_with_wallet,
+ amount=USDCent(600),
+ user_payout_event_manager=user_payout_event_manager,
+ cashout_method_manager=cashout_method_manager,
+ thl_ledger_manager=thl_ledger_manager,
+ user_iphistory_manager=user_iphistory_manager,
+ ip_record_factory=ip_record_factory,
+ thl_redis_client=thl_redis_client,
+ ip="1.1.1.1",
+ )