def setUp(self): Harness.setUp(self) self.david = self.make_participant( "david", is_suspicious=False, mangopay_user_id=self.david_id, mangopay_wallet_id=self.david_wallet_id, email="*****@*****.**", ) self.janet = self.make_participant( "janet", is_suspicious=False, mangopay_user_id=self.janet_id, mangopay_wallet_id=self.janet_wallet_id, email="*****@*****.**", ) self.janet_route = ExchangeRoute.insert(self.janet, "mango-cc", self.card_id) self.homer = self.make_participant( "homer", is_suspicious=False, mangopay_user_id=self.homer_id, mangopay_wallet_id=self.homer_wallet_id, email="*****@*****.**", ) self.homer_route = ExchangeRoute.insert(self.homer, "mango-ba", self.bank_account.Id)
def setUp(self): Harness.setUp(self) self.david = self.make_participant( 'david', mangopay_user_id=self.david_id, mangopay_wallet_id=self.david_wallet_id, email='*****@*****.**') self.janet = self.make_participant( 'janet', mangopay_user_id=self.janet_id, mangopay_wallet_id=self.janet_wallet_id, email='*****@*****.**') self.janet_route = ExchangeRoute.insert(self.janet, 'mango-cc', self.card_id, 'chargeable', currency='EUR') self.homer = self.make_participant( 'homer', mangopay_user_id=self.homer_id, mangopay_wallet_id=self.homer_wallet_id, email='*****@*****.**') self.homer_route = ExchangeRoute.insert(self.homer, 'mango-ba', self.bank_account.Id, 'chargeable')
def make_exchange(self, route, amount, fee, participant, status='succeeded', error='', vat=0): amount = amount if isinstance(amount, Money) else Money(amount, 'EUR') fee = fee if isinstance(fee, Money) else Money(fee, amount.currency) vat = vat if isinstance(vat, Money) else Money(vat, fee.currency) if not isinstance(route, ExchangeRoute): network = route currency = amount.currency if network == 'mango-cc' else None routes = ExchangeRoute.from_network(participant, network, currency=currency) if routes: route = routes[0] else: from .mangopay import MangopayHarness address = MangopayHarness.card_id if network == 'mango-cc' else -participant.id route = ExchangeRoute.insert(participant, network, address, 'chargeable', currency=currency) assert route e_id = record_exchange(self.db, route, amount, fee, vat, participant, 'pre').id record_exchange_result(self.db, e_id, -e_id, status, error, participant) return e_id
def payin_bank_wire(db, participant, debit_amount): """Prepare to receive a bank wire payin. The amount should be how much the user intends to send, not how much will arrive in the wallet. """ route = ExchangeRoute.from_network(participant, 'mango-bw') if not route: route = ExchangeRoute.insert(participant, 'mango-bw', 'x') amount, fee, vat = skim_amount(debit_amount, FEE_PAYIN_BANK_WIRE) e_id = record_exchange(db, route, amount, fee, vat, participant, 'pre') payin = PayIn() payin.AuthorId = participant.mangopay_user_id if not participant.mangopay_wallet_id: create_wallet(db, participant) payin.CreditedWalletId = participant.mangopay_wallet_id payin.ExecutionDetails = PayInExecutionDetailsDirect() payin.PaymentDetails = PayInPaymentDetailsBankWire( DeclaredDebitedFunds=Money(int(debit_amount * 100), 'EUR'), DeclaredFees=Money(int(fee * 100), 'EUR'), ) payin.Tag = str(e_id) try: test_hook() payin = mangoapi.payIns.Create(payin) except Exception as e: error = repr_exception(e) return None, record_exchange_result(db, e_id, 'failed', error, participant) e = record_exchange_result(db, e_id, payin.Status.lower(), repr_error(payin), participant) return payin, e
def payin_bank_wire(db, participant, debit_amount): """Prepare to receive a bank wire payin. The amount should be how much the user intends to send, not how much will arrive in the wallet. """ route = ExchangeRoute.from_network(participant, 'mango-bw') if not route: route = ExchangeRoute.insert(participant, 'mango-bw', 'x') amount, fee, vat = skim_bank_wire(debit_amount) e_id = record_exchange(db, route, amount, fee, vat, participant, 'pre') payin = BankWirePayIn() payin.AuthorId = participant.mangopay_user_id if not participant.mangopay_wallet_id: create_wallet(db, participant) payin.CreditedWalletId = participant.mangopay_wallet_id payin.DeclaredDebitedFunds = Money(int(debit_amount * 100), 'EUR') payin.DeclaredFees = Money(int(fee * 100), 'EUR') payin.Tag = str(e_id) try: test_hook() payin.save() except Exception as e: error = repr_exception(e) return None, record_exchange_result(db, e_id, 'failed', error, participant) e = record_exchange_result(db, e_id, payin.Status.lower(), repr_error(payin), participant) return payin, e
def make_participant(self, username, **kw): platform = kw.pop('elsewhere', 'github') domain = kw.pop('domain', '') kw2 = {} for key in ('last_bill_result', 'balance', 'mangopay_wallet_id'): if key in kw: kw2[key] = kw.pop(key) kind = kw.setdefault('kind', 'individual') if kind not in ('group', 'community'): kw.setdefault('password', 'x') kw.setdefault('session_token', username) i = next(self.seq) kw.setdefault('mangopay_user_id', -i) kw.setdefault('status', 'active') if username: kw['username'] = username if 'join_time' not in kw: kw['join_time'] = utcnow() cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s']*len(vals)) participant = self.db.one(""" INSERT INTO participants ({0}) VALUES ({1}) RETURNING participants.*::participants """.format(cols, placeholders), vals) self.db.run(""" INSERT INTO elsewhere (platform, user_id, user_name, participant, domain) VALUES (%s,%s,%s,%s,%s) """, (platform, participant.id, username, participant.id, domain)) if kind not in ('group', 'community') and participant.mangopay_user_id: wallet_id = kw2.get('mangopay_wallet_id', -participant.id) zero = ZERO[participant.main_currency] self.db.run(""" INSERT INTO wallets (remote_id, balance, owner, remote_owner_id) VALUES (%s, %s, %s, %s) """, (wallet_id, zero, participant.id, participant.mangopay_user_id)) if 'email' in kw: self.db.run(""" INSERT INTO emails (participant, address, verified, verified_time) VALUES (%s, %s, true, now()) """, (participant.id, kw['email'])) if 'last_bill_result' in kw2: ExchangeRoute.insert( participant, 'mango-cc', '-1', kw2['last_bill_result'], currency=participant.main_currency ) if 'balance' in kw2 and kw2['balance'] != 0: self.make_exchange('mango-cc', kw2['balance'], 0, participant) return participant
def test_get_tip_distribution_ignores_bad_cc(self): bad_cc = self.make_participant('bad_cc') ExchangeRoute.insert(bad_cc, 'mango-cc', '-1', 'failed', currency='EUR') self.alice.set_tip_to(self.bob, EUR('1.00')) self.make_payin_and_transfer(self.alice_card, self.bob, EUR('25.00')) bad_cc.set_tip_to(self.bob, EUR('3.00')) expected = ([[EUR('1.00'), 1, EUR('1.00'), EUR('1.00'), 1, Decimal('1')]], 1, EUR('1.00')) actual = self.bob.get_tip_distribution() assert actual == expected
def make_exchange(self, route, amount, fee, participant, status='succeeded', error='', vat=0): if not isinstance(route, ExchangeRoute): network = route route = ExchangeRoute.from_network(participant, network) if not route: from .mangopay import MangopayHarness route = ExchangeRoute.insert(participant, network, MangopayHarness.card_id) assert route e_id = record_exchange(self.db, route, amount, fee, vat, participant, 'pre') record_exchange_result(self.db, e_id, status, error, participant) return e_id
def payout(db, participant, amount): if participant.is_suspicious: raise UserIsSuspicious route = ExchangeRoute.from_network(participant, 'mango-ba') assert route ba = mangoapi.users.GetBankAccount(participant.mangopay_user_id, route.address) # Do final calculations credit_amount, fee = skim_credit(amount, ba) if credit_amount <= 0 or fee / credit_amount > 0.1: raise TransactionFeeTooHigh # Try to dance with MangoPay e_id = record_exchange(db, route, -credit_amount, fee, participant, 'pre') payout = PayOut() payout.AuthorId = participant.mangopay_user_id payout.DebitedFunds = Money(int(credit_amount * 100), 'EUR') payout.DebitedWalletId = participant.mangopay_wallet_id payout.Fees = Money(int(fee * 100), 'EUR') payout.MeanOfPaymentDetails = PayOutPaymentDetailsBankWire( BankAccountId=route.address, BankWireRef=str(e_id), ) payout.Tag = str(e_id) try: test_hook() mangoapi.payOuts.Create(payout) return record_exchange_result(db, e_id, 'created', None, participant) except Exception as e: error = repr_exception(e) return record_exchange_result(db, e_id, 'failed', error, participant)
def test_direct_debit_creation(self, url): path = b'/homer/wallet/payin/direct-debit' data = {'amount': '100.00'} url.return_value = b'https://liberapay.com' + path r = self.client.PxST(path, data, auth_as=self.homer) assert r.code == 403 # rejected because homer has no donations set up self.homer.set_tip_to(self.david, EUR('10.00')) r = self.client.GET(path, auth_as=self.homer) assert b'FRxxxxxxxxxxxxxxxxxxxxx2606' in r.body, r.text r = self.client.POST(path, data, auth_as=self.homer, raise_immediately=False) assert r.code == 200, r.text assert ';url=https://api.sandbox.mangopay.com/' in r.text exchange = self.db.one("SELECT * FROM exchanges") assert exchange.status == 'pre-mandate' route = ExchangeRoute.from_id(exchange.route) path += ('/%s?MandateId=%s' % (exchange.id, route.mandate)).encode('ascii') r = self.client.GET(path, auth_as=self.homer) assert r.code == 200 exchange = self.db.one("SELECT * FROM exchanges") assert exchange.status == 'failed' assert exchange.note == '001833: The Status of this Mandate does not allow for payments'
def setUp(self): Harness.setUp(self) self.david = self.make_participant( 'david', mangopay_user_id=self.david_id, mangopay_wallet_id=self.david_wallet_id, email='*****@*****.**' ) self.janet = self.make_participant( 'janet', mangopay_user_id=self.janet_id, mangopay_wallet_id=self.janet_wallet_id, email='*****@*****.**' ) self.janet_route = ExchangeRoute.insert(self.janet, 'mango-cc', self.card_id) self.homer = self.make_participant( 'homer', mangopay_user_id=self.homer_id, mangopay_wallet_id=self.homer_wallet_id, email='*****@*****.**' ) self.homer_route = ExchangeRoute.insert(self.homer, 'mango-ba', self.bank_account.Id)
def test_payout_amount_under_minimum(self, gba): usd_user = self.make_participant('usd_user', main_currency='USD') route = ExchangeRoute.insert(usd_user, 'mango-ba', 'fake ID', 'chargeable') self.make_exchange('mango-cc', USD(8), 0, usd_user) gba.return_value = self.bank_account_outside_sepa with self.assertRaises(FeeExceedsAmount): payout(self.db, route, USD('0.10'))
def payin_bank_wire(db, participant, debit_amount): """Prepare to receive a bank wire payin. The amount should be how much the user intends to send, not how much will arrive in the wallet. """ route = ExchangeRoute.upsert_bankwire_route(participant) if not isinstance(debit_amount, Money): debit_amount = Money(debit_amount, 'EUR') amount, fee, vat = skim_bank_wire(debit_amount) wallet = participant.get_current_wallet(amount.currency, create=True) e_id = record_exchange(db, route, amount, fee, vat, participant, 'pre').id payin = BankWirePayIn() payin.AuthorId = participant.mangopay_user_id payin.CreditedWalletId = wallet.remote_id payin.DeclaredDebitedFunds = debit_amount.int() payin.DeclaredFees = fee.int() payin.Tag = str(e_id) try: test_hook() payin.save() except Exception as e: error = repr_exception(e) return None, record_exchange_result(db, e_id, '', 'failed', error, participant) e = record_exchange_result( db, e_id, payin.Id, payin.Status.lower(), repr_error(payin), participant ) return payin, e
def payin_bank_wire(db, participant, debit_amount): """Prepare to receive a bank wire payin. The amount should be how much the user intends to send, not how much will arrive in the wallet. """ route = ExchangeRoute.upsert_generic_route(participant, 'mango-bw') if not isinstance(debit_amount, Money): debit_amount = Money(debit_amount, 'EUR') amount, fee, vat = skim_bank_wire(debit_amount) wallet = participant.get_current_wallet(amount.currency, create=True) e_id = record_exchange(db, route, amount, fee, vat, participant, 'pre').id payin = BankWirePayIn() payin.AuthorId = participant.mangopay_user_id payin.CreditedWalletId = wallet.remote_id payin.DeclaredDebitedFunds = Money_to_cents(debit_amount) payin.DeclaredFees = Money_to_cents(fee) payin.Tag = str(e_id) try: test_hook() payin.save() except Exception as e: error = repr_exception(e) return None, record_exchange_result(db, e_id, '', 'failed', error, participant) e = record_exchange_result( db, e_id, payin.Id, payin.Status.lower(), repr_error(payin), participant ) return payin, e
def _test_payin_bank_wire_callback_amount_mismatch(self, Get, fee): homer = self.homer route = ExchangeRoute.insert(homer, 'mango-bw', 'x') e_id = record_exchange(self.db, route, 11, 0, 0, homer, 'pre') assert homer.balance == 0 homer.close(None) assert homer.status == 'closed' qs = "EventType=PAYIN_NORMAL_SUCCEEDED&RessourceId=123456790" payin = BankWirePayIn() payin.Status = 'SUCCEEDED' payin.ResultCode = '000000' payin.ResultMessage = None payin.AuthorId = homer.mangopay_user_id payin.PaymentType = 'BANK_WIRE' payin.DeclaredDebitedFunds = Money(4500, 'EUR') payin.DeclaredFees = Money(100, 'EUR') payin.DebitedFunds = Money(302, 'EUR') payin.Fees = Money(fee, 'EUR') payin.CreditedFunds = Money(302 - fee, 'EUR') payin.Tag = str(e_id) Get.return_value = payin r = self.callback(qs) assert r.code == 200, r.text e = self.db.one("SELECT * FROM exchanges WHERE id = %s", (e_id, )) assert e.amount == D(payin.CreditedFunds.Amount) / D(100) assert e.fee == D(fee) / D(100) assert e.vat == D('0.01') assert e.status == 'succeeded' homer = homer.refetch() assert homer.balance == e.amount assert homer.status == 'active' emails = self.get_emails() assert len(emails) == 1 assert emails[0]['to'][0] == 'homer <%s>' % homer.email assert 'succ' in emails[0]['subject']
def attach_stripe_payment_method(self, participant, stripe_pm_id, one_off=False): pm = stripe.PaymentMethod.retrieve(stripe_pm_id) return ExchangeRoute.attach_stripe_payment_method( participant, pm, one_off)
def test_takes_paid_in_advance(self): team = self.make_participant( 'team', kind='group', accepted_currencies='EUR,USD' ) alice = self.make_participant('alice', main_currency='EUR', accepted_currencies='EUR,USD') team.set_take_for(alice, EUR('1.00'), team) bob = self.make_participant('bob', main_currency='USD', accepted_currencies='EUR,USD') team.set_take_for(bob, EUR('1.00'), team) stripe_account_alice = self.add_payment_account(alice, 'stripe', default_currency='EUR') self.add_payment_account(bob, 'stripe', country='US', default_currency='USD') carl = self.make_participant('carl') carl.set_tip_to(team, EUR('10')) carl_card = ExchangeRoute.insert( carl, 'stripe-card', 'x', 'chargeable', remote_user_id='x' ) payin, pt = self.make_payin_and_transfer(carl_card, team, EUR('10')) assert pt.destination == stripe_account_alice.pk Payday.start().run() transfers = self.db.all("SELECT * FROM transfers ORDER BY id") assert len(transfers) == 1 assert transfers[0].virtual is True assert transfers[0].tipper == carl.id assert transfers[0].tippee == alice.id assert transfers[0].amount == EUR('1')
def test_payout_amount_under_minimum(self, gba): usd_user = self.make_participant('usd_user', main_currency='USD') route = ExchangeRoute.insert(usd_user, 'mango-ba', 'fake ID') self.make_exchange('mango-cc', USD(8), 0, usd_user) gba.return_value = self.bank_account_outside_sepa with self.assertRaises(FeeExceedsAmount): payout(self.db, route, USD('0.10'))
def test_delete_bank_account(self): self.hit('homer', 'delete', 'mango-ba', self.bank_account.Id) homer = Participant.from_username('homer') route = ExchangeRoute.from_address(homer, 'mango-ba', self.bank_account.Id) assert route.status == 'canceled' assert homer.mangopay_user_id
def test_direct_debit_creation(self, url): path = b'/homer/wallet/payin/direct-debit' data = {'amount': '100.00'} url.return_value = b'https://liberapay.com' + path r = self.client.PxST(path, data, auth_as=self.homer) assert r.code == 403 # rejected because homer has no donations set up self.homer.set_tip_to(self.david, EUR('10.00')) r = self.client.GET(path, auth_as=self.homer) assert b'FRxxxxxxxxxxxxxxxxxxxxx2606' in r.body, r.text r = self.client.POST(path, data, auth_as=self.homer, raise_immediately=False) assert r.code == 200, r.text assert ';url=https://api.sandbox.mangopay.com/' in r.text exchange = self.db.one("SELECT * FROM exchanges") assert exchange.status == 'pre-mandate' route = ExchangeRoute.from_id(self.homer, exchange.route) path += ('/%s?MandateId=%s' % (exchange.id, route.mandate)).encode('ascii') r = self.client.GET(path, auth_as=self.homer) assert r.code == 200 exchange = self.db.one("SELECT * FROM exchanges") assert exchange.status == 'failed' assert exchange.note == '001833: The Status of this Mandate does not allow for payments'
def test_takes_paid_in_advance(self): team = self.make_participant( 'team', kind='group', accepted_currencies='EUR,USD' ) alice = self.make_participant('alice', main_currency='EUR', accepted_currencies='EUR,USD') team.set_take_for(alice, EUR('1.00'), team) bob = self.make_participant('bob', main_currency='USD', accepted_currencies='EUR,USD') team.set_take_for(bob, EUR('1.00'), team) stripe_account_alice = self.add_payment_account(alice, 'stripe', default_currency='EUR') self.add_payment_account(bob, 'stripe', default_currency='USD') carl = self.make_participant('carl') carl.set_tip_to(team, EUR('10')) carl_card = ExchangeRoute.insert( carl, 'stripe-card', 'x', 'chargeable', remote_user_id='x' ) payin, pt = self.make_payin_and_transfer(carl_card, team, EUR('10')) assert pt.destination == stripe_account_alice.pk Payday.start().run() transfers = self.db.all("SELECT * FROM transfers ORDER BY id") assert len(transfers) == 1 assert transfers[0].virtual is True assert transfers[0].tipper == carl.id assert transfers[0].tippee == alice.id assert transfers[0].amount == EUR('1')
def test_delete_card(self): self.hit('janet', 'delete', 'mango-cc', self.card_id) janet = Participant.from_username('janet') cards = ExchangeRoute.from_network(janet, 'mango-cc') assert not cards assert janet.mangopay_user_id
def test_associate_nonexistent_card(self, Card_get): Card_get.side_effect = Card.DoesNotExist r = self.client.PxST('/homer/routes/credit-card.json', data={'CardId': '-1'}, auth_as=self.homer) assert r.code == 400 cards = ExchangeRoute.from_network(self.homer, 'mango-cc') assert not cards
def test_payin_bank_wire_callback(self, Get): homer = self.homer route = ExchangeRoute.insert(homer, 'mango-bw', 'x') for status in ('failed', 'succeeded'): status_up = status.upper() error = 'FOO' if status == 'failed' else None e_id = record_exchange(self.db, route, 11, 0, 0, homer, 'pre') assert homer.balance == 0 homer.close(None) assert homer.status == 'closed' qs = "EventType=PAYIN_NORMAL_"+status_up+"&RessourceId=123456790" payin = PayIn() payin.Status = status_up payin.ResultCode = '000001' if error else '000000' payin.ResultMessage = error payin.AuthorId = homer.mangopay_user_id payin.PaymentType = 'BANK_WIRE' payin.Tag = str(e_id) Get.return_value = payin r = self.callback(qs) assert r.code == 200, r.text homer = homer.refetch() if status == 'succeeded': assert homer.balance == 11 assert homer.status == 'active' else: assert homer.balance == 0 assert homer.status == 'closed' emails = self.get_emails() assert len(emails) == 1 assert emails[0]['to'][0] == 'homer <%s>' % homer.email assert status[:4] in emails[0]['subject'] homer.update_status('active') # reset for next loop run
def test_identity_form(self): janeway = self.make_participant( 'janeway', email='*****@*****.**', mangopay_user_id=None, ) assert janeway.mangopay_user_id is None # Create a mangopay natural user data = dict(user_data, terms='agree') kw = dict(auth_as=janeway, raise_immediately=False, xhr=True) r = self.client.POST('/janeway/identity', data, **kw) assert r.code == 200, r.text janeway = janeway.refetch() assert janeway.mangopay_user_id # Test the rendering of the identity page r = self.client.GET('/janeway/identity.html', auth_as=janeway) assert r.code == 200, r.text assert user_data['FirstName'] in r.text # Edit the natural user data2 = dict(data, FirstName='Kathryn', Nationality='US', Birthday='1970-01-01') r = self.client.POST('/janeway/identity', data2, **kw) assert r.code == 200, r.text janeway2 = janeway.refetch() assert janeway2.mangopay_user_id == janeway.mangopay_user_id # Add some money for the next test create_wallet(self.db, janeway, 'EUR') cr = create_card(janeway.mangopay_user_id) route = ExchangeRoute.insert(janeway, 'mango-cc', cr.CardId, 'chargeable', currency='EUR') charge(self.db, route, EUR('20.00'), 'http://127.0.0.1/') # Switch to a legal user data = dict(data2) data['organization'] = 'yes' data['LegalPersonType'] = 'BUSINESS' data['Name'] = 'Starfleet' data['confirmed'] = 'yes' r = self.client.POST('/janeway/identity', data, **kw) assert r.code == 200, r.text janeway = janeway.refetch() assert janeway.mangopay_user_id != janeway2.mangopay_user_id assert janeway.kind == 'organization' self.db.self_check() # Edit the legal user data2 = dict(data, LegalPersonType='ORGANIZATION') r = self.client.POST('/janeway/identity', data2, **kw) assert r.code == 200, r.text janeway2 = janeway.refetch() assert janeway2.mangopay_user_id == janeway.mangopay_user_id
def fake_exchange(db, participant, amount, fee, vat, timestamp): routes = ExchangeRoute.from_network(participant, 'mango-cc', currency='EUR') if routes: route = routes[0] else: route = _fake_thing( db, "exchange_routes", participant=participant.id, network='mango-cc', address='-1', status='chargeable', one_off=False, remote_user_id=participant.mangopay_user_id, currency='EUR', ) e = _fake_thing( db, "exchanges", timestamp=timestamp, participant=participant.id, amount=amount, fee=fee, vat=vat, status='pre', route=route.id, wallet_id='-%i' % participant.id, ) record_exchange_result(db, e.id, -e.id, 'succeeded', '', participant) return e
def fake_exchange(db, participant, amount, fee, vat, timestamp): route = ExchangeRoute.from_network(participant, 'mango-cc') if not route: route = _fake_thing( db, "exchange_routes", participant=participant.id, network='mango-cc', address='-1', error='', one_off=False, ) e = _fake_thing( db, "exchanges", timestamp=timestamp, participant=participant.id, amount=amount, fee=fee, vat=vat, status='pre', route=route.id, ) record_exchange_result(db, e.id, 'succeeded', '', participant) return e
def fake_exchange(db, participant, amount, fee, vat, timestamp): routes = ExchangeRoute.from_network(participant, 'mango-cc') if routes: route = routes[0] else: route = _fake_thing( db, "exchange_routes", participant=participant.id, network='mango-cc', address='-1', error='', one_off=False, remote_user_id=participant.mangopay_user_id, ) e = _fake_thing( db, "exchanges", timestamp=timestamp, participant=participant.id, amount=amount, fee=fee, vat=vat, status='pre', route=route.id, wallet_id=participant.mangopay_wallet_id, ) record_exchange_result(db, e.id, -e.id, 'succeeded', '', participant) return e
def test_negative_paid_in_advance(self): team = self.make_participant('team', kind='group') alice = self.make_participant('alice') team.set_take_for(alice, EUR('1.00'), team) stripe_account_alice = self.add_payment_account(alice, 'stripe') donor = self.make_participant('donor') donor.set_tip_to(team, EUR('5')) donor_card = ExchangeRoute.insert(donor, 'stripe-card', 'x', 'chargeable', remote_user_id='x') payin, pt = self.make_payin_and_transfer(donor_card, team, EUR('10'), 'stripe') assert pt.destination == stripe_account_alice.pk self.db.run("UPDATE takes SET paid_in_advance = -paid_in_advance") Payday.start().run() transfers = self.db.all("SELECT * FROM transfers ORDER BY id") assert len(transfers) == 0
def make_exchange(self, route, amount, fee, participant, status='succeeded', error='', vat=0): amount = amount if isinstance(amount, Money) else Money(amount, 'EUR') fee = fee if isinstance(fee, Money) else Money(fee, amount.currency) vat = vat if isinstance(vat, Money) else Money(vat, fee.currency) if not isinstance(route, ExchangeRoute): network = route currency = amount.currency if network == 'mango-cc' else None routes = ExchangeRoute.from_network(participant, network, currency=currency) if routes: route = routes[0] else: from .mangopay import MangopayHarness address = MangopayHarness.card_id if network == 'mango-cc' else -participant.id route = ExchangeRoute.insert(participant, network, address, currency=currency) assert route e_id = record_exchange(self.db, route, amount, fee, vat, participant, 'pre').id record_exchange_result(self.db, e_id, -e_id, status, error, participant) return e_id
def test_charge_invalidated_card(self): bob = self.make_participant('bob') route = ExchangeRoute.insert(bob, 'mango-cc', '-1', 'canceled', currency='EUR') with self.assertRaises(AssertionError): charge(self.db, route, EUR('10.00'), 'http://localhost/')
def make_participant(self, username, **kw): platform = kw.pop('elsewhere', 'github') kw2 = {} for key in ('last_bill_result', 'balance'): if key in kw: kw2[key] = kw.pop(key) kind = kw.setdefault('kind', 'individual') if kind not in ('group', 'community'): kw.setdefault('password', 'x') kw.setdefault('session_token', username) i = next(self.seq) kw.setdefault('mangopay_user_id', -i) kw.setdefault('mangopay_wallet_id', -i) kw.setdefault('status', 'active') if not 'join_time' in kw: kw['join_time'] = utcnow() cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s'] * len(vals)) participant = self.db.one( """ INSERT INTO participants (username, {0}) VALUES (%s, {1}) RETURNING participants.*::participants """.format(cols, placeholders), (username, ) + vals) self.db.run( """ INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, participant.id)) if 'last_bill_result' in kw2: ExchangeRoute.insert(participant, 'mango-cc', '-1', kw2['last_bill_result']) if 'balance' in kw2 and kw2['balance'] != 0: self.make_exchange('mango-cc', kw2['balance'], 0, participant) return participant
def test_delete_bank_account(self): self.hit('homer', 'delete', 'mango-ba', self.bank_account.Id) homer = Participant.from_username('homer') route = ExchangeRoute.from_address(homer, 'mango-ba', self.bank_account.Id) assert route.error == 'invalidated' assert homer.mangopay_user_id # Check that update_error doesn't update an invalidated route route.update_error('some error') assert route.error == 'invalidated'
def test_delete_bank_account(self): self.hit("homer", "delete", "mango-ba", self.bank_account.Id) homer = Participant.from_username("homer") route = ExchangeRoute.from_address(homer, "mango-ba", self.bank_account.Id) assert route.error == homer.get_bank_account_error() == "invalidated" assert homer.mangopay_user_id # Check that update_error doesn't update an invalidated route route.update_error("some error") assert route.error == homer.get_bank_account_error() == "invalidated"
def make_participant(self, username, **kw): platform = kw.pop('elsewhere', 'github') kw2 = {} for key in ('last_bill_result', 'balance'): if key in kw: kw2[key] = kw.pop(key) kind = kw.setdefault('kind', 'individual') if kind != 'group': kw.setdefault('password', 'x') kw.setdefault('session_token', username) kw.setdefault('status', 'active') if not 'join_time' in kw: kw['join_time'] = utcnow() i = next(self.seq) kw.setdefault('mangopay_user_id', -i) kw.setdefault('mangopay_wallet_id', -i) cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s']*len(vals)) participant = self.db.one(""" INSERT INTO participants (username, {0}) VALUES (%s, {1}) RETURNING participants.*::participants """.format(cols, placeholders), (username,)+vals) self.db.run(""" INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, participant.id)) if 'last_bill_result' in kw2: ExchangeRoute.insert(participant, 'mango-cc', '-1', kw2['last_bill_result']) if 'balance' in kw2: self.make_exchange('mango-cc', kw2['balance'], 0, participant) return participant
def test_identity_form(self): janeway = self.make_participant( 'janeway', email='*****@*****.**', mangopay_user_id=None, ) assert janeway.mangopay_user_id is None # Create a mangopay natural user data = dict(user_data, terms='agree') kw = dict(auth_as=janeway, raise_immediately=False, xhr=True) r = self.client.POST('/janeway/identity-v1', data, **kw) assert r.code == 200, r.text janeway = janeway.refetch() assert janeway.mangopay_user_id # Test the rendering of the identity page r = self.client.GET('/janeway/identity-v1.html', auth_as=janeway) assert r.code == 200, r.text assert user_data['FirstName'] in r.text # Edit the natural user data2 = dict(data, FirstName='Kathryn', Nationality='US', Birthday='1970-01-01') r = self.client.POST('/janeway/identity-v1', data2, **kw) assert r.code == 200, r.text janeway2 = janeway.refetch() assert janeway2.mangopay_user_id == janeway.mangopay_user_id # Add some money for the next test create_wallet(self.db, janeway, 'EUR') cr = create_card(janeway.mangopay_user_id) route = ExchangeRoute.insert(janeway, 'mango-cc', cr.CardId, 'chargeable', currency='EUR') charge(self.db, route, EUR('20.00'), 'http://127.0.0.1/') # Switch to a legal user data = dict(data2) data['organization'] = 'yes' data['LegalPersonType'] = 'BUSINESS' data['Name'] = 'Starfleet' data['confirmed'] = 'yes' r = self.client.POST('/janeway/identity-v1', data, **kw) assert r.code == 200, r.text janeway = janeway.refetch() assert janeway.mangopay_user_id != janeway2.mangopay_user_id assert janeway.kind == 'organization' self.db.self_check() # Edit the legal user data2 = dict(data, LegalPersonType='ORGANIZATION') r = self.client.POST('/janeway/identity-v1', data2, **kw) assert r.code == 200, r.text janeway2 = janeway.refetch() assert janeway2.mangopay_user_id == janeway.mangopay_user_id
def charge(db, participant, amount, return_url): """Charge the participant's credit card. Amount should be the nominal amount. We'll compute fees below this function and add it to amount to end up with charge_amount. """ typecheck(amount, Decimal) if participant.is_suspicious: raise UserIsSuspicious route = ExchangeRoute.from_network(participant, 'mango-cc') assert route charge_amount, fee = upcharge(amount) amount = charge_amount - fee e_id = record_exchange(db, route, amount, fee, participant, 'pre') payin = PayIn() payin.AuthorId = participant.mangopay_user_id if not participant.mangopay_wallet_id: create_wallet(db, participant) payin.CreditedWalletId = participant.mangopay_wallet_id payin.DebitedFunds = Money(int(charge_amount * 100), 'EUR') payin.ExecutionDetails = PayInExecutionDetailsDirect( CardId=route.address, SecureModeReturnURL=return_url, ) payin.Fees = Money(int(fee * 100), 'EUR') payin.PaymentDetails = PayInPaymentDetailsCard( CardType='CB_VISA_MASTERCARD') payin.Tag = str(e_id) try: test_hook() payin = mangoapi.payIns.Create(payin) except Exception as e: error = repr_exception(e) return record_exchange_result(db, e_id, 'failed', error, participant) if payin.ExecutionDetails.SecureModeRedirectURL: raise Response( 302, headers={'Location': payin.ExecutionDetails.SecureModeRedirectURL}) return record_exchange_result(db, e_id, 'succeeded', None, participant)
def fake_exchange(db, participant, amount, fee, timestamp): route = ExchangeRoute.from_network(participant, "mango-cc") if not route: route = _fake_thing( db, "exchange_routes", participant=participant.id, network="mango-cc", address="-1", error="", one_off=False ) e = _fake_thing( db, "exchanges", timestamp=timestamp, participant=participant.id, amount=amount, fee=fee, status="pre", route=route.id, ) record_exchange_result(db, e.id, "succeeded", "", participant) return e
def test_user_page_shows_pledges(self, get_user_info): alice = self.make_elsewhere('github', 1, 'alice').participant bob = self.make_participant('bob') carl = self.make_participant('carl') # bob needs to be an active donor for his pledge to be counted bob.set_tip_to(carl, EUR('1.00')) bob_card = ExchangeRoute.insert( bob, 'stripe-card', 'x', 'chargeable', remote_user_id='x' ) self.add_payment_account(carl, 'stripe') self.make_payin_and_transfer(bob_card, carl, EUR('1.00')) Payday.start().run() # okay, let's check amount = EUR('14.97') bob.set_tip_to(alice, amount) assert alice.receiving == amount r = self.client.GET('/on/github/alice/') assert str(amount.amount) in r.text, r.text
def record_payout_refund(db, payout_refund): orig_payout = BankWirePayOut.get(payout_refund.InitialTransactionId) e_origin = db.one("SELECT * FROM exchanges WHERE id = %s" % (orig_payout.Tag,)) e_refund_id = db.one("SELECT id FROM exchanges WHERE refund_ref = %s", (e_origin.id,)) if e_refund_id: # Already recorded return e_refund_id amount, fee, vat = -e_origin.amount, -e_origin.fee, -e_origin.vat assert payout_refund.DebitedFunds == Money(int(amount * 100), 'EUR') assert payout_refund.Fees == Money(int(fee * 100), 'EUR') route = ExchangeRoute.from_id(e_origin.route) participant = Participant.from_id(e_origin.participant) return db.one(""" INSERT INTO exchanges (amount, fee, vat, participant, status, route, note, refund_ref) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id """, (amount, fee, vat, participant.id, 'created', route.id, None, e_origin.id))
def test_payin_bank_wire_callback(self, Get): homer = self.homer route = ExchangeRoute.insert(homer, 'mango-bw', 'x', 'chargeable') cases = ( ('failed', '000001', 'FOO'), ('failed', '101109', 'The payment period has expired'), ('succeeded', '000000', None), ) for status, result_code, error in cases: status_up = status.upper() e_id = record_exchange(self.db, route, EUR(11), EUR(0), EUR(0), homer, 'pre').id assert homer.balance == 0 homer.close(None) assert homer.status == 'closed' qs = "EventType=PAYIN_NORMAL_" + status_up + "&RessourceId=123456790" payin = BankWirePayIn(Id=-1) payin.Status = status_up payin.ResultCode = result_code payin.ResultMessage = error payin.AuthorId = homer.mangopay_user_id payin.PaymentType = 'BANK_WIRE' payin.DeclaredDebitedFunds = Money(1100, 'EUR') payin.DeclaredFees = Money(0, 'EUR') payin.CreditedFunds = Money(0, 'XXX') if error else Money( 1100, 'EUR') payin.Tag = str(e_id) Get.return_value = payin r = self.callback(qs) assert r.code == 200, r.text homer = homer.refetch() if status == 'succeeded': assert homer.balance == 11 assert homer.status == 'active' else: assert homer.balance == 0 assert homer.status == 'closed' emails = self.get_emails() assert len(emails) == 1 assert emails[0]['to'][0] == 'homer <%s>' % homer.email expected = 'expired' if result_code == '101109' else status[:4] assert expected in emails[0]['subject'] self.db.self_check() homer.update_status('active') # reset for next loop run
def payout(db, participant, amount, ignore_high_fee=False): assert amount > 0 if participant.is_suspended: raise AccountSuspended() payday = db.one("SELECT * FROM paydays WHERE ts_start > ts_end") if payday: raise PaydayIsRunning route = ExchangeRoute.from_network(participant, 'mango-ba') assert route ba = mangoapi.users.GetBankAccount(participant.mangopay_user_id, route.address) # Do final calculations credit_amount, fee, vat = skim_credit(amount, ba) if credit_amount <= 0 and fee > 0: raise FeeExceedsAmount fee_percent = fee / amount if fee_percent > FEE_PAYOUT_WARN and not ignore_high_fee: raise TransactionFeeTooHigh(fee_percent, fee, amount) # Try to dance with MangoPay e_id = record_exchange(db, route, -credit_amount, fee, vat, participant, 'pre') payout = PayOut() payout.AuthorId = participant.mangopay_user_id payout.DebitedFunds = Money(int(amount * 100), 'EUR') payout.DebitedWalletId = participant.mangopay_wallet_id payout.Fees = Money(int(fee * 100), 'EUR') payout.MeanOfPaymentDetails = PayOutPaymentDetailsBankWire( BankAccountId=route.address, BankWireRef=str(e_id), ) payout.Tag = str(e_id) try: test_hook() payout = mangoapi.payOuts.Create(payout) return record_exchange_result(db, e_id, payout.Status.lower(), repr_error(payout), participant) except Exception as e: error = repr_exception(e) return record_exchange_result(db, e_id, 'failed', error, participant)
def record_unexpected_payin(db, payin): """Record an unexpected bank wire payin. """ assert payin.PaymentType == 'BANK_WIRE' debited_amount = payin.DebitedFunds / Decimal(100) paid_fee = payin.Fees / Decimal(100) vat = skim_bank_wire(debited_amount)[2] wallet_id = payin.CreditedWalletId participant = Participant.from_mangopay_user_id(payin.AuthorId) current_wallet = participant.get_current_wallet(debited_amount.currency) assert current_wallet.remote_id == wallet_id route = ExchangeRoute.upsert_bankwire_route(participant) amount = debited_amount - paid_fee return db.one(""" INSERT INTO exchanges (amount, fee, vat, participant, status, route, note, remote_id, wallet_id) VALUES (%s, %s, %s, %s, 'created', %s, NULL, %s, %s) RETURNING id """, (amount, paid_fee, vat, participant.id, route.id, payin.Id, wallet_id))
def record_unexpected_payin(db, payin): """Record an unexpected bank wire payin. """ assert payin.PaymentType == 'BANK_WIRE' debited_amount = payin.DebitedFunds / Decimal(100) paid_fee = payin.Fees / Decimal(100) vat = skim_bank_wire(debited_amount)[2] wallet_id = payin.CreditedWalletId participant = Participant.from_mangopay_user_id(payin.AuthorId) current_wallet = participant.get_current_wallet(debited_amount.currency) assert current_wallet.remote_id == wallet_id route = ExchangeRoute.upsert_generic_route(participant, 'mango-bw') amount = debited_amount - paid_fee return db.one(""" INSERT INTO exchanges (amount, fee, vat, participant, status, route, note, remote_id, wallet_id) VALUES (%s, %s, %s, %s, 'created', %s, NULL, %s, %s) RETURNING id """, (amount, paid_fee, vat, participant.id, route.id, payin.Id, wallet_id))
def charge(db, participant, amount, return_url): """Charge the participant's credit card. Amount should be the nominal amount. We'll compute fees below this function and add it to amount to end up with charge_amount. """ typecheck(amount, Decimal) if participant.is_suspicious: raise UserIsSuspicious route = ExchangeRoute.from_network(participant, 'mango-cc') assert route charge_amount, fee = upcharge(amount) amount = charge_amount - fee e_id = record_exchange(db, route, amount, fee, participant, 'pre') payin = PayIn() payin.AuthorId = participant.mangopay_user_id if not participant.mangopay_wallet_id: create_wallet(db, participant) payin.CreditedWalletId = participant.mangopay_wallet_id payin.DebitedFunds = Money(int(charge_amount * 100), 'EUR') payin.ExecutionDetails = PayInExecutionDetailsDirect( CardId=route.address, SecureModeReturnURL=return_url, ) payin.Fees = Money(int(fee * 100), 'EUR') payin.PaymentDetails = PayInPaymentDetailsCard(CardType='CB_VISA_MASTERCARD') payin.Tag = str(e_id) try: test_hook() payin = mangoapi.payIns.Create(payin) except Exception as e: error = repr_exception(e) return record_exchange_result(db, e_id, 'failed', error, participant) if payin.ExecutionDetails.SecureModeRedirectURL: raise Response(302, headers={'Location': payin.ExecutionDetails.SecureModeRedirectURL}) return record_exchange_result(db, e_id, 'succeeded', None, participant)
def test_payin_bank_wire_callback(self, Get): homer = self.homer route = ExchangeRoute.insert(homer, 'mango-bw', 'x') cases = ( ('failed', '000001', 'FOO'), ('failed', '101109', 'The payment period has expired'), ('succeeded', '000000', None), ) for status, result_code, error in cases: status_up = status.upper() e_id = record_exchange(self.db, route, EUR(11), EUR(0), EUR(0), homer, 'pre').id assert homer.balance == 0 homer.close(None) assert homer.status == 'closed' qs = "EventType=PAYIN_NORMAL_"+status_up+"&RessourceId=123456790" payin = BankWirePayIn(Id=-1) payin.Status = status_up payin.ResultCode = result_code payin.ResultMessage = error payin.AuthorId = homer.mangopay_user_id payin.PaymentType = 'BANK_WIRE' payin.DeclaredDebitedFunds = Money(1100, 'EUR') payin.DeclaredFees = Money(0, 'EUR') payin.CreditedFunds = Money(0, 'XXX') if error else Money(1100, 'EUR') payin.Tag = str(e_id) Get.return_value = payin r = self.callback(qs) assert r.code == 200, r.text homer = homer.refetch() if status == 'succeeded': assert homer.balance == 11 assert homer.status == 'active' else: assert homer.balance == 0 assert homer.status == 'closed' emails = self.get_emails() assert len(emails) == 1 assert emails[0]['to'][0] == 'homer <%s>' % homer.email expected = 'expired' if result_code == '101109' else status[:4] assert expected in emails[0]['subject'] self.db.self_check() homer.update_status('active') # reset for next loop run
def record_payout_refund(db, payout_refund): orig_payout = BankWirePayOut.get(payout_refund.InitialTransactionId) e_origin = db.one("SELECT * FROM exchanges WHERE id = %s", (orig_payout.Tag,)) e_refund_id = db.one("SELECT id FROM exchanges WHERE refund_ref = %s", (e_origin.id,)) if e_refund_id: # Already recorded return e_refund_id amount, fee, vat = -e_origin.amount, -e_origin.fee, -e_origin.vat assert payout_refund.DebitedFunds / 100 == amount assert payout_refund.Fees / 100 == fee route = ExchangeRoute.from_id(e_origin.route) participant = Participant.from_id(e_origin.participant) remote_id = payout_refund.Id wallet_id = e_origin.wallet_id return db.one(""" INSERT INTO exchanges (amount, fee, vat, participant, status, route, note, refund_ref, remote_id, wallet_id) VALUES (%s, %s, %s, %s, 'created', %s, NULL, %s, %s, %s) RETURNING id """, (amount, fee, vat, participant.id, route.id, e_origin.id, remote_id, wallet_id))
def record_payout_refund(db, payout_refund): orig_payout = BankWirePayOut.get(payout_refund.InitialTransactionId) e_origin = db.one("SELECT * FROM exchanges WHERE id = %s", (orig_payout.Tag,)) e_refund_id = db.one("SELECT id FROM exchanges WHERE refund_ref = %s", (e_origin.id,)) if e_refund_id: # Already recorded return e_refund_id amount, fee, vat = -e_origin.amount, -e_origin.fee, -e_origin.vat assert payout_refund.DebitedFunds / 100 == amount assert payout_refund.Fees / 100 == fee participant = Participant.from_id(e_origin.participant) route = ExchangeRoute.from_id(participant, e_origin.route) remote_id = payout_refund.Id wallet_id = e_origin.wallet_id return db.one(""" INSERT INTO exchanges (amount, fee, vat, participant, status, route, note, refund_ref, remote_id, wallet_id) VALUES (%s, %s, %s, %s, 'created', %s, NULL, %s, %s, %s) RETURNING id """, (amount, fee, vat, participant.id, route.id, e_origin.id, remote_id, wallet_id))
def test_negative_paid_in_advance(self): team = self.make_participant('team', kind='group') alice = self.make_participant('alice') team.set_take_for(alice, EUR('1.00'), team) stripe_account_alice = self.add_payment_account(alice, 'stripe') donor = self.make_participant('donor') donor.set_tip_to(team, EUR('5')) donor_card = ExchangeRoute.insert( donor, 'stripe-card', 'x', 'chargeable', remote_user_id='x' ) payin, pt = self.make_payin_and_transfer(donor_card, team, EUR('10')) assert pt.destination == stripe_account_alice.pk self.db.run("UPDATE takes SET paid_in_advance = -paid_in_advance") Payday.start().run() transfers = self.db.all("SELECT * FROM transfers ORDER BY id") assert len(transfers) == 0
def payout(db, participant, amount, ignore_high_fee=False): assert amount > 0 payday = db.one("SELECT * FROM paydays WHERE ts_start > ts_end") if payday: raise PaydayIsRunning route = ExchangeRoute.from_network(participant, 'mango-ba') assert route ba = mangoapi.users.GetBankAccount(participant.mangopay_user_id, route.address) # Do final calculations credit_amount, fee, vat = skim_credit(amount, ba) if credit_amount <= 0 and fee > 0: raise FeeExceedsAmount fee_percent = fee / amount if fee_percent > FEE_CREDIT_WARN and not ignore_high_fee: raise TransactionFeeTooHigh(fee_percent, fee, amount) # Try to dance with MangoPay e_id = record_exchange(db, route, -credit_amount, fee, vat, participant, 'pre') payout = PayOut() payout.AuthorId = participant.mangopay_user_id payout.DebitedFunds = Money(int(amount * 100), 'EUR') payout.DebitedWalletId = participant.mangopay_wallet_id payout.Fees = Money(int(fee * 100), 'EUR') payout.MeanOfPaymentDetails = PayOutPaymentDetailsBankWire( BankAccountId=route.address, BankWireRef=str(e_id), ) payout.Tag = str(e_id) try: test_hook() mangoapi.payOuts.Create(payout) return record_exchange_result(db, e_id, 'created', None, participant) except Exception as e: error = repr_exception(e) return record_exchange_result(db, e_id, 'failed', error, participant)
def test_payin_bank_wire_callback(self, Get): homer = self.homer route = ExchangeRoute.insert(homer, 'mango-bw', 'x') for status in ('failed', 'succeeded'): status_up = status.upper() error = 'FOO' if status == 'failed' else None e_id = record_exchange(self.db, route, 11, 0, 0, homer, 'pre') assert homer.balance == 0 homer.close(None) assert homer.status == 'closed' qs = "EventType=PAYIN_NORMAL_" + status_up + "&RessourceId=123456790" payin = PayIn() payin.Status = status_up payin.ResultCode = '000001' if error else '000000' payin.ResultMessage = error payin.AuthorId = homer.mangopay_user_id payin.PaymentType = 'BANK_WIRE' pd = payin.PaymentDetails = PayInPaymentDetailsBankWire() pd.DeclaredDebitedFunds = Money(1100, 'EUR').__dict__ pd.DeclaredFees = Money(0, 'EUR').__dict__ payin.CreditedFunds = Money(1100, 'EUR') payin.Tag = str(e_id) Get.return_value = payin r = self.callback(qs) assert r.code == 200, r.text homer = homer.refetch() if status == 'succeeded': assert homer.balance == 11 assert homer.status == 'active' else: assert homer.balance == 0 assert homer.status == 'closed' emails = self.get_emails() assert len(emails) == 1 assert emails[0]['to'][0] == 'homer <%s>' % homer.email assert status[:4] in emails[0]['subject'] homer.update_status('active') # reset for next loop run