Пример #1
0
 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__)
Пример #2
0
 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'),
     }
Пример #3
0
    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__)
Пример #4
0
 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__)
Пример #5
0
 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__)
Пример #6
0
 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
Пример #7
0
 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
Пример #8
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
Пример #9
0
 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))
Пример #10
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
Пример #11
0
 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,
     }
Пример #12
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()
Пример #13
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()
Пример #14
0
 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))