def transfer_for_real(self, transfers): db = self.db print("Starting transfers (n=%i)" % len(transfers)) msg = "Executing transfer #%i (amount=%s context=%s team=%s tipper_wallet_id=%s tippee_wallet_id=%s)" for t in transfers: log(msg % (t.id, t.amount, t.context, t.team, t.tipper_wallet_id, t.tippee_wallet_id)) transfer(db, **t.__dict__)
def test_dispute_callback_lost(self, save, get_payin, get_dispute): self.make_participant( 'LiberapayOrg', kind='organization', balance=EUR('100.00'), mangopay_user_id='0', mangopay_wallet_id='0', ) save.side_effect = fake_transfer e_id = self.make_exchange('mango-cc', EUR('16'), EUR('1'), self.janet) dispute = Dispute() dispute.Id = '-1' dispute.CreationDate = utcnow() dispute.DisputedFunds = Money(1700, 'EUR') dispute.DisputeType = 'CONTESTABLE' dispute.InitialTransactionType = 'PAYIN' get_dispute.return_value = dispute payin = PayIn(tag=str(e_id)) get_payin.return_value = payin # Transfer some of the money to homer transfer(self.db, self.janet.id, self.homer.id, EUR('3.68'), 'tip') # Withdraw some of the money self.make_exchange('mango-ba', EUR('-2.68'), 0, self.homer) # Add a bit of money that will remain undisputed, to test bundle swapping self.make_exchange('mango-cc', EUR('0.32'), 0, self.janet) self.make_exchange('mango-cc', EUR('0.55'), 0, self.homer) # Call back self.db.self_check() for status in ('CREATED', 'CLOSED'): dispute.Status = status if status == 'CLOSED': dispute.ResultCode = 'LOST' qs = "EventType=DISPUTE_" + status + "&RessourceId=123456790" r = self.callback(qs, raise_immediately=True) assert r.code == 200, r.text self.db.self_check() # Check final state balances = dict( self.db.all("SELECT username, balance FROM participants")) assert balances == { '_chargebacks_': EUR('16.00'), 'david': 0, 'homer': 0, 'janet': 0, 'LiberapayOrg': EUR('98.19'), } debts = dict(((r[0], r[1]), r[2]) for r in self.db.all(""" SELECT p_debtor.username AS debtor, p_creditor.username AS creditor, sum(d.amount) FROM debts d JOIN participants p_debtor ON p_debtor.id = d.debtor JOIN participants p_creditor ON p_creditor.id = d.creditor WHERE d.status = 'due' GROUP BY p_debtor.username, p_creditor.username """)) assert debts == { ('janet', 'LiberapayOrg'): EUR('1.00'), ('janet', 'homer'): EUR('3.36'), ('homer', 'LiberapayOrg'): EUR('1.81'), }
def transfer_for_real(self, transfers): db = self.db print("Starting transfers (n=%i)" % len(transfers)) msg = "%s transfer #%i (amount=%s in_advance=%s context=%s team=%s)%s" for t in transfers: if t.amount: delay = getattr(self, 'transfer_delay', 0) action = 'Executing' when = ' in %.2f seconds' % delay if delay else ' now' else: delay = 0 action = 'Recording' when = '' log(msg % (action, t.id, t.amount, t.in_advance, t.context, t.team, when)) if delay: sleep(delay) if t.in_advance: db.run( """ INSERT INTO transfers (tipper, tippee, amount, context, team, invoice, status, wallet_from, wallet_to, virtual) VALUES (%(tipper)s, %(tippee)s, %(in_advance)s, %(context)s, %(team)s, %(invoice)s, 'succeeded', 'x', 'y', true); WITH current_tip AS ( SELECT t.id FROM current_tips t WHERE t.tipper = %(tipper)s AND t.tippee = COALESCE(%(team)s, %(tippee)s) ) UPDATE tips t SET paid_in_advance = (t.paid_in_advance - %(in_advance)s) FROM current_tip t2 WHERE t.id = t2.id; """, t.__dict__) if t.team: db.run( """ WITH current_take AS ( SELECT t.id FROM current_takes t WHERE t.team = %(team)s AND t.member = %(tippee)s ) UPDATE takes t SET paid_in_advance = ( coalesce_currency_amount(t.paid_in_advance, t.amount::currency) - convert(%(in_advance)s, t.amount::currency) ) FROM current_take t2 WHERE t.id = t2.id; """, t.__dict__) if t.amount: transfer(db, **t.__dict__)
def transfer_for_real(self, transfers): db = self.db print("Starting transfers (n=%i)" % len(transfers)) msg = "Executing transfer #%i (amount=%s context=%s team=%s tipper_wallet_id=%s tippee_wallet_id=%s) %s" for t in transfers: delay = getattr(self, 'transfer_delay', 0) when = 'in %.2f seconds' % delay if delay else 'now' log(msg % (t.id, t.amount, t.context, t.team, t.tipper_wallet_id, t.tippee_wallet_id, when)) if delay: sleep(delay) transfer(db, **t.__dict__)
def test_4_sync_with_mangopay_records_transfer_success(self): self.make_exchange('mango-cc', 10, 0, self.janet) with mock.patch('liberapay.billing.transactions.record_transfer_result') as rtr: rtr.side_effect = Foobar() with self.assertRaises(Foobar): transfer(self.db, self.janet.id, self.david.id, EUR('10.00'), 'tip') t = self.db.one("SELECT * FROM transfers") assert t.status == 'pre' sync_with_mangopay(self.db) t = self.db.one("SELECT * FROM transfers") assert t.status == 'succeeded' assert Participant.from_username('david').balance == 10 assert Participant.from_username('janet').balance == 0
def test_3_sync_with_mangopay_handles_transfers_that_didnt_happen(self): self.make_exchange('mango-cc', 10, 0, self.janet) with mock.patch('liberapay.billing.transactions._record_transfer_result') as rtr, \ mock.patch('liberapay.billing.transactions.Transfer.save', autospec=True) as save: rtr.side_effect = save.side_effect = Foobar with self.assertRaises(Foobar): transfer(self.db, self.janet.id, self.david.id, EUR('10.00'), 'tip') t = self.db.one("SELECT * FROM transfers") assert t.status == 'pre' self.throw_transactions_back_in_time() sync_with_mangopay(self.db) t = self.db.one("SELECT * FROM transfers") assert t.status == 'failed' assert t.error == 'interrupted' assert Participant.from_username('david').balance == 0 assert Participant.from_username('janet').balance == 10
def settle_debts(db): while True: with db.get_cursor() as cursor: debt = cursor.one(""" SELECT d.id, d.debtor AS tipper, d.creditor AS tippee, d.amount , 'debt' AS context , p_debtor.mangopay_user_id AS tipper_mango_id , p_debtor.mangopay_wallet_id AS tipper_wallet_id , p_creditor.mangopay_user_id AS tippee_mango_id , p_creditor.mangopay_wallet_id AS tippee_wallet_id FROM debts d JOIN participants p_debtor ON p_debtor.id = d.debtor JOIN participants p_creditor ON p_creditor.id = d.creditor WHERE d.status = 'due' AND p_debtor.balance >= d.amount AND p_creditor.status = 'active' LIMIT 1 FOR UPDATE OF d """) if not debt: break try: t_id = transfer(db, **debt._asdict())[1] except NegativeBalance: continue cursor.run( """ UPDATE debts SET status = 'paid' , settlement = %s WHERE id = %s """, (t_id, debt.id))
def test_dispute_callback_won(self, save, get_payin, get_dispute): self.make_participant('LiberapayOrg', kind='organization') save.side_effect = fake_transfer e_id = self.make_exchange('mango-cc', EUR('16'), EUR('1'), self.janet) dispute = Dispute() dispute.Id = '-1' dispute.CreationDate = utcnow() dispute.DisputedFunds = Money(1700, 'EUR') dispute.DisputeType = 'CONTESTABLE' dispute.InitialTransactionType = 'PAYIN' get_dispute.return_value = dispute payin = PayIn(tag=str(e_id)) get_payin.return_value = payin # Transfer some of the money to homer transfer(self.db, self.janet.id, self.homer.id, EUR('3.68'), 'tip') # Withdraw some of the money self.make_exchange('mango-ba', EUR('-2.68'), 0, self.homer) # Add money that will remain undisputed, to test bundle swapping self.make_exchange('mango-cc', EUR('2.69'), 0, self.janet) # Call back self.db.self_check() for status in ('CREATED', 'CLOSED'): dispute.Status = status if status == 'CLOSED': dispute.ResultCode = 'WON' qs = "EventType=DISPUTE_" + status + "&RessourceId=123456790" r = self.callback(qs) assert r.code == 200, r.text self.db.self_check() # Check final state disputed = self.db.all("SELECT * FROM cash_bundles WHERE disputed") debts = self.db.all("SELECT * FROM debts") assert not disputed assert not debts balances = dict( self.db.all("SELECT username, balance FROM participants")) assert balances == { 'david': 0, 'homer': EUR('1.00'), 'janet': EUR('15.01'), 'LiberapayOrg': 0, }
def test_cash_bundles_are_merged_after_transfer(self): bundles_count = lambda: self.db.one("SELECT count(*) FROM cash_bundles") assert bundles_count() == 0 self.make_exchange('mango-cc', 45, 0, self.janet) assert bundles_count() == 1 transfer(self.db, self.janet.id, self.homer.id, EUR('10.00'), 'tip') assert bundles_count() == 2 transfer(self.db, self.homer.id, self.janet.id, EUR('5.00'), 'tip') assert bundles_count() == 2 transfer(self.db, self.homer.id, self.janet.id, EUR('5.00'), 'tip') assert bundles_count() == 1 self.db.self_check()
def settle_debts(db): while True: with db.get_cursor() as cursor: debt = cursor.one(""" SELECT d.id, d.debtor AS tipper, d.creditor AS tippee, d.amount , 'debt' AS context , w_debtor.remote_owner_id AS tipper_mango_id , w_debtor.remote_id AS tipper_wallet_id , w_creditor.remote_owner_id AS tippee_mango_id , w_creditor.remote_id AS tippee_wallet_id FROM debts d JOIN wallets w_debtor ON w_debtor.owner = d.debtor AND w_debtor.balance::currency = d.amount::currency AND w_debtor.is_current IS TRUE LEFT JOIN wallets w_creditor ON w_creditor.owner = d.creditor AND w_creditor.balance::currency = d.amount::currency AND w_creditor.is_current IS TRUE JOIN participants p_creditor ON p_creditor.id = d.creditor WHERE d.status = 'due' AND w_debtor.balance >= d.amount AND p_creditor.status = 'active' LIMIT 1 FOR UPDATE OF d """) if not debt: break try: t_id = transfer(db, **debt._asdict())[1] except NegativeBalance: continue cursor.run(""" UPDATE debts SET status = 'paid' , settlement = %s WHERE id = %s """, (t_id, debt.id))