diff --git a/deploy/before.sql b/deploy/before.sql new file mode 100644 index 0000000000..1242a462ed --- /dev/null +++ b/deploy/before.sql @@ -0,0 +1,23 @@ +BEGIN; + CREATE TYPE follow_up AS ENUM ('monthly', 'quarterly', 'yearly', 'never'); + CREATE TABLE payments_for_open_source + ( uuid text PRIMARY KEY + , ctime timestamptz NOT NULL DEFAULT now() + + -- card charge + , amount bigint NOT NULL + , transaction_id text UNIQUE NOT NULL + + -- contact info + , name text NOT NULL + , follow_up follow_up NOT NULL + , email_address text NOT NULL + , message_id bigint REFERENCES email_messages(id) + + -- promotion details + , promotion_name text NOT NULL DEFAULT '' + , promotion_url text NOT NULL DEFAULT '' + , promotion_twitter text NOT NULL DEFAULT '' + , promotion_message text NOT NULL DEFAULT '' + ); +END; diff --git a/gratipay/__init__.py b/gratipay/__init__.py index b291e77c72..6572b379e7 100644 --- a/gratipay/__init__.py +++ b/gratipay/__init__.py @@ -4,23 +4,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals from gratipay import utils - - -def pay_for_open_source(app, raw): - parsed, errors = _parse(raw) - if not errors: - transaction_id = _charge_card(app, parsed) - if not transaction_id: - errors.append('charge_card') - if not errors: - message_id = None - if parsed['email_address']: - message_id = _send_receipt(app, parsed['email_address']) - if not message_id: - errors.append('send_receipt') - _store_info(parsed, transaction_id, message_id) - parsed = {} - return {'parsed': parsed, 'errors': errors} +from gratipay.models.payment_for_open_source import PaymentForOpenSource def _parse(raw): @@ -90,14 +74,31 @@ def _parse(raw): return parsed, errors -def _charge_card(app, parsed): +def _charge(app, parsed): raise NotImplementedError -def _send_receipt(app, parsed): +def _send(app, parsed): raise NotImplementedError app.email_queue.put() -def _store_info(parsed, transaction_id, message_id): - raise NotImplementedError +def _store(parsed, transaction_id, message_id): + PaymentForOpenSource.insert(transaction_id=transaction_id, message_id=message_id, **parsed) + + +def pay_for_open_source(app, raw, _parse=_parse, _charge=_charge, _send=_send, _store=_store): + parsed, errors = _parse(raw) + if not errors: + transaction_id = _charge(app, parsed) + if not transaction_id: + errors.append('charge_card') + if not errors: + message_id = None + if parsed['email_address']: + message_id = _send(app, parsed['email_address']) + if not message_id: + errors.append('send_receipt') + _store(parsed, transaction_id, message_id) + parsed= {} + return {'parsed': parsed, 'errors': errors} diff --git a/gratipay/models/__init__.py b/gratipay/models/__init__.py index 4ae554ec56..9660fa8606 100644 --- a/gratipay/models/__init__.py +++ b/gratipay/models/__init__.py @@ -16,10 +16,12 @@ from .exchange_route import ExchangeRoute from .package import Package from .participant import Participant +from .payment_for_open_source import PaymentForOpenSource from .team import Team -MODELS = (AccountElsewhere, Community, Country, ExchangeRoute, Package, Participant, Team) +MODELS = (AccountElsewhere, Community, Country, ExchangeRoute, Package, Participant, + PaymentForOpenSource, Team) @contextmanager diff --git a/gratipay/models/payment_for_open_source.py b/gratipay/models/payment_for_open_source.py new file mode 100644 index 0000000000..f68dfb9a87 --- /dev/null +++ b/gratipay/models/payment_for_open_source.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function, unicode_literals + +import uuid as uuidlib +from postgres.orm import Model + + +class PaymentForOpenSource(Model): + + typname = "payments_for_open_source" + + def __repr__(self): + return ''.format(repr(self.amount)) + + + @classmethod + def from_uuid(cls, uuid, cursor=None): + return (cursor or cls.db).one(""" + SELECT pfos.*::payments_for_open_source + FROM payments_for_open_source pfos + WHERE uuid = %s + """, (uuid,)) + + + @classmethod + def insert(cls, amount, transaction_id, name, follow_up, email_address, message_id, + promotion_name, promotion_url, promotion_twitter, promotion_message, + cursor=None): + uuid = uuidlib.uuid4().hex + return (cursor or cls.db).one(""" + INSERT INTO payments_for_open_source + (uuid, amount, transaction_id, name, follow_up, email_address, message_id, + promotion_name, promotion_url, promotion_twitter, promotion_message) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + RETURNING payments_for_open_source.*::payments_for_open_source + """, (uuid, amount, transaction_id, name, follow_up, email_address, message_id, + promotion_name, promotion_url, promotion_twitter, promotion_message)) diff --git a/gratipay/testing/harness.py b/gratipay/testing/harness.py index ce5e7b0872..ca3eb790d4 100644 --- a/gratipay/testing/harness.py +++ b/gratipay/testing/harness.py @@ -21,6 +21,7 @@ from gratipay.models.exchange_route import ExchangeRoute from gratipay.models.package import NPM, Package from gratipay.models.participant import Participant, MAX_TIP, MIN_TIP +from gratipay.models.payment_for_open_source import PaymentForOpenSource from gratipay.models.team import Team from gratipay.security import user from gratipay.testing import P @@ -142,6 +143,24 @@ def clear_tables(self): self.db.run("INSERT INTO worker_coordination DEFAULT VALUES") + def make_payment_for_open_source(self, **info): + defaults = dict( amount='1000' + , name='Alice Liddell' + , transaction_id='deadbeef' + , email_address='alice@example.com' + , follow_up='monthly' + , message_id=None # TODO call gratipay._send and grab id + , promotion_name='Wonderland' + , promotion_url='http://www.example.com/' + , promotion_twitter='thebestbutter' + , promotion_message='Love me! Love me! Say that you love me!' + ) + for key, value in defaults.items(): + if key not in info: + info[key] = value + return PaymentForOpenSource.insert(**info) + + def make_elsewhere(self, platform, user_id, user_name, **kw): """Factory for :py:class:`~gratipay.models.account_elsewhere.AccountElsewhere`. """ @@ -318,6 +337,7 @@ def make_exchange(self, route, amount, fee, participant, status='succeeded', err record_exchange_result(self.db, e_id, status, error, participant) return e_id + def make_payment(self, participant, team, amount, direction, payday, timestamp=utcnow()): """Factory for payment""" diff --git a/tests/py/test_payments_for_open_source.py b/tests/py/test_payments_for_open_source.py new file mode 100644 index 0000000000..d9f2af60e6 --- /dev/null +++ b/tests/py/test_payments_for_open_source.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function, unicode_literals + +from gratipay.testing import Harness +from gratipay.models.payment_for_open_source import PaymentForOpenSource + + +class Tests(Harness): + + def test_can_insert(self): + self.make_payment_for_open_source() + assert self.db.one('SELECT * FROM payments_for_open_source').name == 'Alice Liddell' + + def test_can_fetch(self): + uuid = self.make_payment_for_open_source().uuid + assert PaymentForOpenSource.from_uuid(uuid).name == 'Alice Liddell'