def test_make_web_cashier_withdraw_detail_request(self): # This is the easiest way to get a user's balance. don't axe me. dummy_transaction = CashTransaction(self.user) initial_balance = dummy_transaction.get_balance_amount() self.assertEqual(initial_balance, 0) # Mock the response responses.add( responses.GET, WebCashierPaymentDetailRequest.url, body=str(json.dumps(WEB_CACHIER_PAYMENT_DETAIL_REQUEST_SUCCESS)), status=200, content_type='application/json' ) req = make_web_cashier_payment_detail_request(self.user, 'tid', 'sid') # Now test the the proper transactions and stuff were created. # Get any GidxTransactions that were created because of this payment. cash_transaction = GidxTransaction.objects.filter( merchant_transaction_id=WEB_CACHIER_PAYMENT_DETAIL_REQUEST_SUCCESS[ 'MerchantTransactionID']) # make sure at least one 'sale' transaction was created (based on the current dummy data, # there should be 1 since we ignore non-sale transactions) self.assertGreater(cash_transaction.count(), 0) # Now make sure the counts match. self.assertEqual(cash_transaction.count(), 1) # Make sure the user's balance has been udpated. # Hard code the amount in case something get's goofed and it ends up as 0 or something. new_balance = dummy_transaction.get_balance_amount() self.assertEqual(new_balance, 20) self.user.delete()
def create_withdrawal(user, amount, balance_result): test = CashTransaction(user) test.withdraw(amount) tran_det= CashTransactionDetail.objects.get( user=test.transaction_detail.user, transaction=test.transaction_detail.transaction) self.assertAlmostEquals(tran_det.amount, -amount) bal = CashBalance.objects.get( user=test.transaction_detail.user) self.assertAlmostEquals(bal.amount,balance_result)
def form_valid(self, form): user = self.request.user cleaned_data = form.cleaned_data payment_method_nonce = self.request.POST.get('payment_method_nonce', None) # # Error out of there is not message if payment_method_nonce is None: messages.error(self.request, 'Did not receive response from payment gateway') return HttpResponseRedirect(self.failure_redirect_url) # # Attempts the transaction via braintree setting # customers pk and email in the braintree database amount = cleaned_data['amount'] result = braintree.Transaction.sale({ "amount": amount, "payment_method_nonce": payment_method_nonce, "customer": { "id": user.pk, "email": user.email, }, }) # # If the transaction is a success we return a success # message, create the database transaction, and # link the braintree transaction id with the dfs # transaction. if result.is_success: messages.success( self.request, 'The deposit was a success!', ) trans = CashTransaction(user) trans.deposit_braintree(amount, result.transaction.id) # Create a task that will send the user an email confirming the transaction try: send_deposit_receipt.delay( user, amount, trans.get_balance_string_formatted(), timezone.now()) except Exception as e: logger.error(e) client.captureException(e) return HttpResponseRedirect(self.success_redirect_url) # # On failure we redirect them and report the transaction # failure to the user. else: messages.error( self.request, result.transaction.processor_response_text, ) return HttpResponseRedirect(self.failure_redirect_url)
def setUp(self): super().setUp() self.user = self.get_admin_user() # get a superuser self.withdraw_amount = 10.00 # using float here on purpose self.account_balance = 10000.00 # balance starting amount (once we add it) ct = CashTransaction(self.user) # get a new CashTransaction instance ct.deposit(self.account_balance ) # start the balance with self.account_balance tm = TaxManager(self.user) tm.set_tax_id("123456789")
def create_user(self, username): """ creates a user and gives them $10,000 :param username: :return: """ user = User.objects.create(username=username) user.set_password(self.DEFAULT_USER_PASSWORD) user.save() Information.objects.create(user=user) ct = CashTransaction(user) ct.deposit(10000.00) return user
def deposits_for_period(self): """ Get the user's deposits for period of time. """ cash_transaction = CashTransaction(self.user) limits = self.user.limits deposits = 0 if limits.exists(): deposit_limit = self.user.limits.get(type=Limit.DEPOSIT) deposits = \ cash_transaction.get_all_deposits(date_range=deposit_limit.time_period_boundaries)[ 'amount__sum'] return deposits
def withdraw(self, amount): """ This method creates a cash withdrawal transaction and the appropriate book keeping for allowing :param amount: :return: """ # The first time withdraw is called, withdraw_object and withdraw_object.status will both not be set. # On a successful withdraw, withdraw_object and withdraw_object.status will both be set. # On an invalid withdraw (amount 0, amount too large, etc), # withdraw_object will be set but withdraw_object.status will not. # If withdraw_object is set, we want to raise a WithdrawCalledTwice exception if withdraw_object.status is set # or if it does not exist. if self.withdraw_object: try: if self.withdraw_object.status: raise WithdrawCalledTwiceException( type(self).__name__, 'withdraw() can only be called on the object one time') except: raise WithdrawCalledTwiceException( type(self).__name__, 'withdraw() can only be called on the object one time') amount = Decimal(amount) # # if this instance wasnt created by pk, its not set/created yet. if self.withdraw_object == None: logger.info('creating withdraw_object in parent') self.withdraw_object = self.withdraw_class() tm = TaxManager(self.user) self.withdraw_object.net_profit = tm.calendar_year_net_profit() # # throws exceptions if insufficient funds, or tax info required logger.info('parent - call validate_withdraw') self.validate_withdraw(amount) # # Performs the transaction since everything has been validated # and saves the the transaction detail with the withdraw object. logger.info('parent - create the cash transaction') ct = CashTransaction(self.user) ct.withdraw(amount) self.withdraw_object.cash_transaction_detail = ct.transaction_detail self.withdraw_object.status = self.get_withdraw_status( WithdrawStatusConstants.Pending.value) self.withdraw_object.save() # save in db
def test_check_cancel_payout(self): cw = self.__make_withdrawal_check(self.withdraw_amount) pending = models.WithdrawStatus.objects.get( pk=WithdrawStatusConstants.Pending.value) self.assertEquals(cw.withdraw_object.status, pending) cancelled = models.WithdrawStatus.objects.get( pk=WithdrawStatusConstants.CancelledAdminDefault.value) cw.cancel() self.assertEqual(cw.withdraw_object.status, cancelled) ct = CashTransaction(self.user) self.assertAlmostEquals(ct.get_balance_amount(), decimal.Decimal(self.account_balance))
def __refund_entry(self, entry): """ refund a single entry. THIS DOES NOT remove the entry from a contest! :param entry: :return: """ buyin = entry.contest_pool.prize_structure.buyin bm = BuyinManager(entry.user) transaction = None # Create a cash or ticket deposit as a refund, # based on what the user used to get into the contest if bm.entry_did_use_ticket(entry): tm = TicketManager(entry.user) tm.deposit(buyin) transaction = tm.transaction refund = self.__create_refund(transaction, entry) else: ct = CashTransaction(entry.user) ct.deposit(buyin) transaction = ct.transaction refund = self.__create_refund(transaction, entry) # Create refund transaction from escrow escrow_ct = CashTransaction(self.get_escrow_user()) escrow_ct.withdraw(buyin, trans=transaction) return refund
def validate_withdraw(self, amount): """ Sets the standard withdraw data in the :class:`cash.withdraw.models.Withdraw` abstract model. """ if amount < Decimal(0.0): raise AmountNegativeException( type(self).__name__, 'amount: ' + str(amount)) # # make sure they dont have more than the max outstanding withdraw requests max_pending = PendingMax().value() user_pending_withdraws = self.withdraw_class.objects.filter( cash_transaction_detail__user=self.user, status__in=self.OUTSTANDING_WITHDRAW_STATUSES) if len(user_pending_withdraws) >= max_pending: raise MaxCurrentWithdrawsException( type(self).__name__, "user at max pending withdraws") # # less than minimum ? greater than maximum ? we dont want that cashout = WithdrawMinMax() if amount < cashout.get_min() or cashout.get_max() < amount: raise CashoutWithdrawOutOfRangeException( type(self).__name__, "the amount must be within the range ($%s to $%s)" % (str(cashout.get_min()), str(cashout.get_max()))) # # Checks to make sure we are not withdrawing more than we have ct = CashTransaction(self.user) balance = ct.get_balance_amount() if balance < amount: raise OverdraftException(self.user.username) # # Gets the profit for the year before the current withdrawal # and raises an exception if their tax information needs to # be collected. current_year_profit = ct.get_withdrawal_amount_current_year() if (current_year_profit + amount ) >= settings.DFS_CASH_WITHDRAWAL_AMOUNT_REQUEST_TAX_INFO: # # Checks to see if we collected tax information for the user tm = TaxManager(self.user) if not tm.info_collected(): raise TaxInformationException(self.user.username)
def setUp(self): super().setUp() self.withdraw_amount = decimal.Decimal(10.00) # a decimal amount self.account_balance = 1000.00 # balance starting amount (once we add it) self.user = self.get_admin_user() # r = self.move_time(days=365, hours=1) # adjust time test ct = CashTransaction(self.user) # get a new CashTransaction instance ct.deposit(self.account_balance ) # start the balance with self.account_balance information = AccountInformation( self.user) # give self.user an Information object information.set_fields(fullname='Ryan', address1='address1', city='city', state='NH', zipcode='03820') r.restore()
def cancel(self): # old args: , withdraw_pk, status_pk ): """ Cancels a withdraw assuming the status is still pending. Releases the funds back to user. :param withdraw_pk: :param status_pk: """ self.__check_status_pending() self.withdraw_object.status = self.get_withdraw_status( WithdrawStatusConstants.CancelledAdminDefault.value) self.withdraw_object.save() # # Creates a new transaction for the refunded amount. category = TransactionType.objects.get( pk=TransactionTypeConstants.AdminCancelWithdraw.value) ct = CashTransaction(self.user) ct.deposit(abs(self.withdraw_object.cash_transaction_detail.amount), category)
def setUp(self): super().setUp() self.user1 = self.get_basic_user("test1") self.user2 = self.get_basic_user("test2") self.user3 = self.get_basic_user("test3") self.build_world() self.user1_ct = CashTransaction(self.user1) self.user1_ct.deposit(100) self.user2_ct = CashTransaction(self.user2) self.user2_ct.deposit(50) ta = TicketAmount.objects.get(amount=10.00) self.user3_tm = TicketManager(self.user3) self.user3_tm.deposit(10) self.escrow_user = self.user2_ct.get_escrow_user() self.escrow_ct = CashTransaction(self.escrow_user) bm = BuyinManager(self.user1) bm.buyin(self.contest_pool) bm = BuyinManager(self.user2) bm.buyin(self.contest_pool) bm = BuyinManager(self.user3) bm.buyin(self.contest_pool) Entry.objects.filter(contest_pool=self.contest_pool).update( contest=self.contest)
def __convert_bonus_cash(user, rake_paid, transaction): """ Creates the conversion from bonus cash to real cash based on the rake_paid for the given entry :param user: :param rake_paid: """ bct = BonusCashTransaction(user) balance = bct.get_balance_amount() # # Create the conversion if there is a balance # to the user's bonus cash account if balance > 0: # # get the conversion amount based on rake paid amount = rake_paid * settings.BONUS_CASH_RAKE_PERCENTAGE # # round to the nearest cent val = math.floor(amount * 100) amount = val / 100.0 # # if the amount is greater than the balance make the # amount the balance if balance < amount: amount = balance # # create the withdraw from the bonus cash bct.withdraw(amount, transaction) # # create the deposit from the bonus cash ct = CashTransaction(user) ct.deposit(amount, trans=bct.transaction)
def test_multiple_withdraw_gidx(self): # Test Transaction.deposit_gidx mulitple times with the same merchant_transaction_id cash_trans = CashTransaction(self.user) mti = 'fake_transaction_id' cash_trans.withdraw_gidx(10, mti) # Should be 1 transaction gidx_transactions = GidxTransaction.objects.filter(merchant_transaction_id=mti) self.assertEqual(gidx_transactions.count(), 1) # Now try again with the same merchant_transaction_id and it should not allow another # transcation to be made cash_trans.withdraw_gidx(10, mti) gidx_transactions = GidxTransaction.objects.filter(merchant_transaction_id=mti) self.assertEqual(gidx_transactions.count(), 1) self.user.delete()
def __update_accounts(self, place, contest, entry, amount): """ Updates the accounts for Payout, FPP, Bonus, and Rake This gets run on each contest entry once the contest is finished. It: 1. Withdraws the payout amount from escrow's account. 2. Deposits that amount into the entry owner's account. 3. Pays out any Frequent Player Points earned. 4. :param place: :param contest: :param entry: :param amount: :return: """ payout = Payout() payout.rank = place payout.contest = contest payout.entry = entry tm = CashTransaction(entry.lineup.user) tm.deposit(amount) # # Take cash out of escrow ct = CashTransaction(self.get_escrow_user()) ct.withdraw(amount, tm.transaction) payout.transaction = tm.transaction payout.save() user = payout.entry.lineup.user rake_paid = contest.buyin * .10 # # Pays out FPP lsm = LoyaltyStatusManager(user) fppt = FppTransaction(user) # rake * base loyalty multiplier * the multiplier fppt.deposit((rake_paid * lsm.BASE_LOYALTY_MULTIPLIER) * lsm.get_fpp_multiplier(), trans=ct.transaction) fpp = FPP() fpp.contest = contest fpp.transaction = ct.transaction fpp.save() # # convert the bonus_cash for the user self.__convert_bonus_cash(user, rake_paid, payout.transaction) # # Create a rake transaction for the user rpt = RakepaidTransaction(user) rpt.deposit(rake_paid, trans=payout.transaction) msg = "User[" + payout.entry.lineup.user.username + "] was ranked #" + str( payout.rank) + " for contest #" + str( payout.contest.pk) + " and was paid out." Logger.log(ErrorCodes.INFO, "Contest Payout", msg)
def test_create_first_deposit(self): ct = CashTransaction(self.user) ct.deposit( self.amount ) self.assertAlmostEquals( ct.get_balance_amount(), self.amount )
def get(request, amount): # first make sure it's a float. try: amount = float(amount) except ValueError: return Response( data={ "status": "FAIL", "detail": "Please enter a valid USD amount", }, status=400, ) # Enforce minimum withdraw amount. if float(amount) < 5: return Response( data={ "status": "FAIL", "detail": "Minimum withdraw amount is $5.00.", }, status=400, ) # Ensure the user has the funds available for withdrawal. ct = CashTransaction(request.user) has_funds = ct.check_sufficient_funds(float(amount)) if not has_funds: return Response( data={ "status": "FAIL", "detail": "You do not have the funds to cover this withdrawal amount.", "reasonCodes": [], }, status=400, ) web_cashier = WebCashierCreatePayoutSession( ip_address=get_client_ip(request), user=request.user, amount=amount) # Make the request. web_cashier_response = web_cashier.send() message = web_cashier_response.get_response_message() # If we didn't receive a JS embed... if not message: return Response( data={ "status": "FAIL", "detail": "We were unable to initiate the withdraw process.", "reasonCodes": web_cashier_response.get_reason_codes(), }, status=400, ) return Response( data={ "status": "SUCCESS", "detail": message, "reasonCodes": web_cashier_response.get_reason_codes(), }, status=200, )
def post(request): merchant_session_id = request.data.get('session_id') if merchant_session_id is None: return Response( data={"detail": "Session ID is missing"}, status=400, ) # get the merchant session entry that initiated this session. gidx_session = GidxSession.objects.get( session_id=merchant_session_id, service_type='WebCashier_CreateSession') session_data = gidx_session.request_data # Check that they are the correct user for this session if not gidx_session.user == request.user: raise PermissionDenied( detail='Requesting user does not own this session.') if not session_data.get('PayActionCode') == 'PAYOUT': logger.error( 'Attempated to GidxWithdrawSessionComplete a session that is not a ' 'PAYOUT: session %s' % gidx_session) return Response( data={"detail": ":("}, status=400, ) # Make sure we can determine the amount of the withdrawal amount = session_data.get('CashierPaymentAmount', {}).get('PaymentAmount') if not amount: logger.error( 'Attempated to GidxWithdrawSessionComplete a session that has no ' 'amount: session %s' % gidx_session) return Response( data={"detail": ":/"}, status=400, ) # Ensure there is not an exiting cash transaction for this transaction. merchant_transaction_id = session_data.get('MerchantTransactionID') existing_transaction = GidxTransaction.objects.filter( merchant_transaction_id=merchant_transaction_id) if existing_transaction.count() > 0: logger.error( 'Attempated to GidxWithdrawSessionComplete a session that has an existing ' 'transaction!: session %s | merchant_transaction_id' % (gidx_session, merchant_transaction_id)) return Response( data={"detail": ">:"}, status=400, ) # ok, so we know that the session we have is a valid withdraw that does not already # have a transaction associated with it. This means we can initiate a withdraw. trans = CashTransaction(request.user) trans.withdraw_gidx(float(amount), merchant_transaction_id) # As long as nothing errors out, send a 200 back to the browser. return Response( data={"detail": "cool."}, status=200, )
def make_web_cashier_payment_detail_request(self, user, transaction_id, session_id): """ A task that will make a request to GIDX for transaction details, and will handle the response appropriately (adding funds, etc). :param self: :param user: :param transaction_id: :param session_id: :return: """ payment_detail_request = WebCashierPaymentDetailRequest( user=user, merchant_transaction_id=transaction_id, merchant_session_id=session_id, ) payment_detail_response = payment_detail_request.send() transaction_id = payment_detail_response.json['MerchantTransactionID'] logger.info('Payment detail received: %s' % payment_detail_response.json) # The response contains payments... if payment_detail_request.has_payments(): payments = payment_detail_request.get_cash_payments() for payment in payments: # was it a credit (deposit) or debit (withdraw)? payment_type = payment['PaymentAmountType'] # Create a gidx cash transaction which will save the transaction to the db and # update the user's balance. trans = CashTransaction(user) # # It is a DEPOSIT if payment_type.lower() == 'credit': if payment['PaymentStatusMessage'] == 'Failed': logger.warning( 'Deposit payment was not a success, not adding funds. %s' % payment) elif payment['PaymentStatusMessage'] == 'Complete': # Deposit the amount into their account trans.deposit_gidx(payment['PaymentAmount'], transaction_id) else: raise Exception( 'Unknown PaymentStatusMessage from GIDX payment detail response: %s' % (payment)) # # It is a PAYOUT elif payment_type.lower() == 'debit': existing_transaction = GidxTransaction.objects.filter( merchant_transaction_id=transaction_id) # There is already more than one transaction with this ID, we should be # notified of this. if existing_transaction.count() > 1: # Send some useful information to Sentry. client.context.merge({ 'extra': { 'response_json': payment_detail_response.json, 'merchant_transaction_id': transaction_id, } }) client.captureMessage( "More than one transaction for a merchant_transaction_id was found." ) client.context.clear() # The withdraw failed - We need to issue a refund if a withdraw was already # made (it probalby was). if payment['PaymentStatusMessage'].lower() == 'failed': # We had a previous withdraw transaction, put the money back into the user's # balance. if existing_transaction.count() > 0: logger.info( 'Withdraw was not a success, refunding previous withdraw. %s' % payment) trans.deposit_gidx(payment['PaymentAmount'], "%s--REFUND" % transaction_id) else: logger.warning( 'No transaction exists for this failed withdraw attempt: %s' % (payment_detail_response.json)) # Withdraw was a success! We need to withdraw if we haven't already. elif payment['PaymentStatusMessage'].lower() == 'complete': # They've never had a transaction to reduce their balance! we need to do it now. if existing_transaction.count() == 0: logger.info( 'No withdraw transaction exists, creating one now. %s' % (payment_detail_response.json)) trans.withdraw_gidx(payment['PaymentAmount'], transaction_id) else: logger.info( 'A withdraw transaction exists, NOT creating one now. %s' % (payment_detail_response.json)) else: raise Exception( 'Unknown PaymentStatusMessage from GIDX payment detail response: %s' % (payment)) else: raise Exception( 'Unknown PaymentAmountType from GIDX payment detail response: %s' % (payment)) else: logger.warning('Payment Detail contained no payment info! %s' % payment_detail_response.json) return payment_detail_request
def __get_trigger_transaction(self): ct = CashTransaction(self.user) ct.deposit(1) return ct.transaction
def test(self): """ Test the additional functionality of Cash Transaction that was not implemented in the :class:`transaction.classes.Transaction` class. """ # USERNAME = '******' # AMOUNT= decimal.Decimal(5.24) # AMOUNT_2 = decimal.Decimal(5.38) # AMOUNT_3_NEGATIVE = decimal.Decimal(3.33) # AMOUNT_4 = decimal.Decimal(100.00) # user = self.get_user(USERNAME) def create_deposit(user, amount, balance_result): test = CashTransaction(user) test.deposit(amount) tran_det= CashTransactionDetail.objects.get( user=test.transaction_detail.user, transaction=test.transaction_detail.transaction) self.assertAlmostEquals(tran_det.amount, amount) bal = CashBalance.objects.get( user=test.transaction_detail.user) self.assertAlmostEquals(bal.amount,balance_result) #self.assertEquals(bal.transaction, tran_det) def create_withdrawal(user, amount, balance_result): test = CashTransaction(user) test.withdraw(amount) tran_det= CashTransactionDetail.objects.get( user=test.transaction_detail.user, transaction=test.transaction_detail.transaction) self.assertAlmostEquals(tran_det.amount, -amount) bal = CashBalance.objects.get( user=test.transaction_detail.user) self.assertAlmostEquals(bal.amount,balance_result) #self.assertEquals(bal.transaction, tran_det) # # Tests deposit running_total = self.AMOUNT create_deposit(self.user, self.AMOUNT, running_total ) # # Tests second deposit and balance running_total += self.AMOUNT_2 create_deposit(self.user, self.AMOUNT_2, running_total) # # Tests that balance gets updated when there is a creation # of an additional NEGATIVE transaction_detail running_total -= self.AMOUNT_3_NEGATIVE create_withdrawal(self.user, self.AMOUNT_3_NEGATIVE, running_total) # # # if you delete the balance while there are transactions, # # the balance will start over at 0.00, although # # you would be able to recalculate the actual balance # # using the historical user transactions. the balance is just a counter # # Test with multiple transactions and no existence of a balance # running_total += self.AMOUNT_4 # CashBalance.objects.get(user=self.user).delete() # create_deposit(self.user, self.AMOUNT_4, running_total) # # Tests creation of object with an object that is not a user self.assertRaises(IncorrectVariableTypeException, lambda: CashTransaction(1))
class RefundTest(AbstractTest, RefundBuildWorldMixin): def setUp(self): super().setUp() self.user1 = self.get_basic_user("test1") self.user2 = self.get_basic_user("test2") self.user3 = self.get_basic_user("test3") self.build_world() self.user1_ct = CashTransaction(self.user1) self.user1_ct.deposit(100) self.user2_ct = CashTransaction(self.user2) self.user2_ct.deposit(50) ta = TicketAmount.objects.get(amount=10.00) self.user3_tm = TicketManager(self.user3) self.user3_tm.deposit(10) self.escrow_user = self.user2_ct.get_escrow_user() self.escrow_ct = CashTransaction(self.escrow_user) bm = BuyinManager(self.user1) bm.buyin(self.contest_pool) bm = BuyinManager(self.user2) bm.buyin(self.contest_pool) bm = BuyinManager(self.user3) bm.buyin(self.contest_pool) Entry.objects.filter(contest_pool=self.contest_pool).update( contest=self.contest) def test_refund(self): self.assertEqual(self.user1_ct.get_balance_amount(), 90) self.assertEqual(self.user2_ct.get_balance_amount(), 40) self.assertEqual(self.escrow_ct.get_balance_amount(), 30) self.assertEqual(self.user3_tm.get_available_tickets().count(), 0) refund_manager = RefundManager() refund_manager.refund(self.contest, force=True) self.assertEqual(self.user1_ct.get_balance_amount(), 100) self.assertEqual(self.user2_ct.get_balance_amount(), 50) self.assertEqual(self.escrow_ct.get_balance_amount(), 0) self.assertEqual(self.user3_tm.get_available_tickets().count(), 1) # in progress should be refundable in cases # where its not a GPP and it did not fill # def test_contest_is_in_progress(self): # self.contest.status = self.contest.INPROGRESS # self.contest.save() # self.should_raise_contest_is_in_progress_or_closed_exception() def test_contest_is_cancelled(self): self.contest.status = self.contest.CANCELLED self.contest.save() self.should_raise_contest_is_cancelled_or_closed() def test_contest_is_closed(self): self.contest.status = self.contest.CLOSED self.contest.save() self.should_raise_contest_is_cancelled_or_closed() # completed is an "in progress" status, which has # no more live inprogress games # def test_contest_is_completed(self): # self.contest.status = self.contest.COMPLETED # self.contest.save() # self.should_raise_contest_is_in_progress_or_closed_exception() def should_raise_contest_is_cancelled_or_closed(self): self.assertEqual(self.user1_ct.get_balance_amount(), 90) self.assertEqual(self.user2_ct.get_balance_amount(), 40) self.assertEqual(self.escrow_ct.get_balance_amount(), 30) self.assertEqual(self.user3_tm.get_available_tickets().count(), 0) refund_manager = RefundManager() self.assertRaises( ContestCanNotBeRefunded, lambda: refund_manager.refund(self.contest, force=True)) self.assertEqual(self.user1_ct.get_balance_amount(), 90) self.assertEqual(self.user2_ct.get_balance_amount(), 40) self.assertEqual(self.escrow_ct.get_balance_amount(), 30) self.assertEqual(self.user3_tm.get_available_tickets().count(), 0)
def get(self, request): user = self.request.user cash_transaction = CashTransaction(user) serializer = self.serializer_class(cash_transaction) return Response(serializer.data)
def get_balance(self): return CashTransaction(self.user).get_balance_amount()
def __payout_contest(self, contest): """ Method assumes the contest has never been paid out and is ready to be paid out. This is why the method is private and should be called from a separate method that does the individual error checking. """ try: c = ClosedContest.objects.get(pk=contest.pk) # print('%s already closed & paid out.'%str(contest)) return # go no further except: pass # # get the prize pool ranks for the contest ranks = Rank.objects.filter( prize_structure=contest.prize_structure).order_by('rank') # # get the entries for the contest entries = Entry.objects.filter(contest=contest) entries = entries.order_by('-lineup__fantasy_points') # # Validate the ranks are setup properly for rank in ranks: # # verify the abstract amount is correct type if not isinstance(rank.amount, AbstractAmount): raise mysite.exceptions.IncorrectVariableTypeException( type(self).__name__, 'rank') # # Get the transaction class and verify that it can deposit transaction_class = rank.amount.get_transaction_class() if not issubclass(transaction_class, CanDeposit): raise mysite.exceptions.IncorrectVariableTypeException( type(self).__name__, 'transaction_class') # print('------- entries [%s] contest [%s] -------' %(str(len(entries)), str(contest))) # # perform the payouts by going through each entry and finding # ties and ranks for the ties to chop. # print('======= ranks [%s] =======' % (str(ranks))) # # we now need to check which is shorter: the list of ranks or the list of entries, # and only use that many ranks for calculating winners! (its possible for # fewer entries than the total number of ranks! if len(entries) < len(ranks): logger.info('SUPERLAY PAYOUT CONTEST: %s' % contest) i = 0 while i < len(ranks[:len(entries)]): # print('++++ i (rank): %s +++++' % str(i) ) entries_to_pay = list() ranks_to_pay = list() entries_to_pay.append(entries[i]) ranks_to_pay.append(ranks[i]) score = entries[i].lineup.fantasy_points # # For each tie add the user to the list to chop the payment # and add the next payout to be split with the ties. while i + 1 < len(entries) and score == entries[ i + 1].lineup.fantasy_points: i += 1 entries_to_pay.append(entries[i]) if len(ranks) > i: ranks_to_pay.append(ranks[i]) self.__payout_spot(ranks_to_pay, entries_to_pay, contest) i += 1 # ############################################################### # rank all of the entries with the same payout algorithm. ############################################################### # j = 0 # last_fantasy_points = None # for entry in entries: # if last_fantasy_points is None: # last_fantasy_points = entry.lineup.fantasy_points # count_at_rank = Entry.objects.filter(contest=contest, lineup__fantasy_points=) # using the fantasy_points as the key, add/increment the entry id to the list. # the length of that list will be the # of entries at that rank, and # the rank will be the order of the keys. entry_fantasy_points_map = {} for entry in entries: try: entry_fantasy_points_map[entry.lineup.fantasy_points] += [ entry.pk ] except KeyError: entry_fantasy_points_map[entry.lineup.fantasy_points] = [ entry.pk ] # sort the fantasy points map on the map key (ascending) sorted_list = sorted(entry_fantasy_points_map.items(), key=lambda x: x[0]) # so its descending ie: [(75.5, [432, 213]), (50.25, [431234, 234534]), (25.0, [1, 123])] sorted_list.reverse() entry_rank = 1 for fantasy_points, entry_id_list in sorted_list: count_at_rank = len(entry_id_list) Entry.objects.filter(pk__in=entry_id_list).update( final_rank=entry_rank) entry_rank += count_at_rank # Determine what our net rake amount was. rake_transaction = None rake_post_overlay = calculate_rake(contest) # We made money on rake! No overlay, yaaay if rake_post_overlay > 0: # # Take cash out of escrow and deposit it into draftboard logger.info(('We made money on this contest, creating a Rake ' 'transaction for $%s. contest: %s') % (rake_post_overlay, contest)) escrow_withdraw_trans = CashTransaction(self.get_escrow_user()) escrow_withdraw_trans.withdraw(rake_post_overlay) draftboard_deposit_trans = CashTransaction( self.get_draftboard_user()) draftboard_deposit_trans.deposit( rake_post_overlay, trans=escrow_withdraw_trans.transaction) rake_transaction = escrow_withdraw_trans.transaction # We lost money on rake. :( elif rake_post_overlay < 0: # # Take cash out of draftboard and deposit it into escrow logger.info(('We lost money on this contest, creating a Rake ' 'transaction for $%s. contest: %s') % (rake_post_overlay, contest)) rake_post_overlay = abs(rake_post_overlay) draftboard_withdraw_trans = CashTransaction( self.get_draftboard_user()) draftboard_withdraw_trans.withdraw(rake_post_overlay) escrow_deposit_trans = CashTransaction(self.get_escrow_user()) escrow_deposit_trans.deposit( rake_post_overlay, trans=draftboard_withdraw_trans.transaction) rake_transaction = draftboard_withdraw_trans.transaction # We broke even on this contest, don't create a rake transaction below. elif rake_post_overlay == 0: logger.info( 'No rake was collected, not creating a Rake transaction. contest: %s' % contest) if rake_transaction: # links the contest with the rake payout rake = Rake() rake.contest = contest rake.transaction = rake_transaction rake.save() contest.status = Contest.CLOSED contest.save()
def setUp(self): super().setUp() # ensure the default ticket TicketManager.create_default_ticket_amounts(verbose=False) # add funds to user self.user = self.get_basic_user() ct = CashTransaction(self.user) ct.deposit(100) # salary_generator = Dummy.generate_salaries() # self.salary_pool = salary_generator.pool # start # # self.verbose = True # set to False to disable print statements # # The sport we are going to build fake stats for. # Lets use nfl, but it doesnt matter what sport we use self.sport = 'nfl' # # Ensure there are Games by using the Dummy to generate fake stats. # The ScheduleManager requires that Game objects exist # because when it creates scheduled Contest objects # it is required to create a draft group. self.dummy = Dummy(sport=self.sport) self.generator = self.dummy.generate() self.salary_pool = self.generator.pool self.site_sport = self.dummy.site_sport # stash the site_sport for easy use self.site_sport_manager = SiteSportManager() self.game_model = self.site_sport_manager.get_game_class( self.site_sport) # ie: sports.nfl.models.Game self.games = self.game_model.objects.all() # there should be handful now, for today if self.games.count() <= 0: raise Exception( 'buyin.tests.BuyinTest - we meant to create games.... but none were created!') # end # create a simple prize pool self.first = 100.0 self.second = 50.0 self.third = 25.0 self.buyin = 10 cps = CashPrizeStructureCreator(name='test') cps.add(1, self.first) cps.add(2, self.second) cps.add(3, self.third) cps.set_buyin(self.buyin) cps.save() cps.prize_structure.save() self.prize_structure = cps.prize_structure self.ranks = cps.ranks # # create the Contest # now = timezone.now() # start = DfsDateTimeUtil.create(now.date(), time(23,0)) # end = DfsDateTimeUtil.create(now.date() + timedelta(days=1), time(0,0)) start = self.games[0].start + timedelta(minutes=5) end = self.games[self.games.count() - 1].start # set 'end' to start of last game cc = ContestCreator("test_contest", self.sport, self.prize_structure, start, end) self.draft_group2 = DraftGroup() self.draft_group2.salary_pool = self.salary_pool self.draft_group2.start = start self.draft_group2.end = end self.draft_group2.save() self.contest_pool, created = ContestPoolCreator( self.sport, self.prize_structure, start, (end - start).seconds * 60, self.draft_group2 ).get_or_create() self.contest = cc.create() self.contest.status = Contest.RESERVABLE self.contest.save() self.draft_group = DraftGroup() self.draft_group.salary_pool = self.salary_pool self.draft_group.start = start self.draft_group.end = end self.draft_group.save()
def validate_escrow_is_zero(self): ct = CashTransaction(AbstractManagerClass.get_escrow_user()) ct.get_balance_amount() self.assertEqual(ct.get_balance_amount(), decimal.Decimal(0.0))
def fund_user_account(self, user): ct = CashTransaction(user) ct.deposit(100)
def buyin(self, contest_pool, lineup=None): """ Creates the buyin for the user based on the ContestPool and lineup. Lineup can be null or not passed to allow for reserving contest spots. This should be only run in a task :param contest_pool: the ContestPool to register in :param lineup: assumed the lineup is validated on creation :raises :class:`contest.exceptions.ContestCouldNotEnterException`: When the there is a race condition and the contest cannot be entered after max_retries :raises :class:`contest.exceptions.ContestIsNotAcceptingLineupsException`: When contest_pool does not have a draftgroup, because it is not accepting teams yet. :raises :class:`contest.exceptions.ContestLineupMismatchedDraftGroupsException`: When the lineup was picked from a draftgroup that does not match the contest_pool. :raises :class:`contest.exceptions.ContestIsInProgressOrClosedException`: When The contest_pool has started, or its past the start time :raises :class:`contest.exceptions.LineupDoesNotMatchUser`: When the lineup is not owned by the user. :raises :class:`contest.exceptions.ContestMaxEntriesReachedException`: When the max entries is reached by the lineup. """ # validate the contest and the lineup are allowed to be created self.lineup_contest(contest_pool, lineup) # Create either the ticket or cash transaction # Try to pay with a ticket first, if that doesn't work because the user doesn't have any # tickets, then try to pay with their cash balance. tm = TicketManager(self.user) # Get the transaction type - `ContestBuyin` # category = TransactionType.objects.get(pk=TransactionTypeConstants.ContestBuyin) try: tm.consume(amount=contest_pool.prize_structure.buyin) # keep a reference of the transaction. transaction = tm.transaction except (UserDoesNotHaveTicketException, ticket.models.TicketAmount.DoesNotExist): # Paying via Ticket failed, Create a cash transaciton with the type of 'ContestBuyin'. ct = CashTransaction(user=self.user) # Make the transaction a withdrawal. ct.withdraw(amount=contest_pool.prize_structure.buyin) # keep a reference of the transaction for user later. transaction = ct.transaction # # Transfer money into escrow escrow_ct = CashTransaction(self.get_escrow_user()) escrow_ct.deposit(contest_pool.prize_structure.buyin, trans=transaction) # # Create the Entry entry = Entry() entry.contest_pool = contest_pool # entry.contest = contest # the contest will be set later when the ContestPool starts entry.contest = None entry.lineup = lineup entry.user = self.user entry.save() # # Create the Buyin model buyin = Buyin() buyin.contest_pool = contest_pool buyin.transaction = transaction # buyin.contest = contest # the contest will be set later when the ContestPool starts (?) buyin.contest = None buyin.entry = entry buyin.save() # # Increment the contest_entry variable contest_pool.current_entries = F('current_entries') + 1 contest_pool.save() contest_pool.refresh_from_db() msg = "User[" + self.user.username + "] bought into the contest_pool #" \ + str(contest_pool.pk) + " with entry #" + str(entry.pk) Logger.log(ErrorCodes.INFO, "ContestPool Buyin", msg) # # pusher contest updates because entries were changed ContestPoolPush(ContestPoolSerializer(contest_pool).data).send() return entry