diff --git a/gratipay/homepage.py b/gratipay/homepage.py index a183288803..e189998998 100644 --- a/gratipay/homepage.py +++ b/gratipay/homepage.py @@ -13,10 +13,10 @@ def _parse(raw): """ errors = [] - x = lambda f: raw.get(f, '').strip() + x = lambda f: raw[f].strip() # KeyError -> 400 # amount - amount = x('amount') or '0' + amount = x('amount') if (not amount.isdigit()) or (int(amount) < 10): errors.append('amount') amount = ''.join(x for x in amount.split('.')[0] if x.isdigit()) @@ -105,6 +105,7 @@ def pay_for_open_source(app, raw): payment_method_nonce = parsed.pop('payment_method_nonce') payment_for_open_source = _store(parsed) if not errors: + import pdb; pdb.set_trace() result = _charge(parsed['amount'], payment_method_nonce) payment_for_open_source.process_result(result) if not payment_for_open_source.succeeded: diff --git a/gratipay/models/payment_for_open_source.py b/gratipay/models/payment_for_open_source.py index 905a122e2a..e8f6e1fc9e 100644 --- a/gratipay/models/payment_for_open_source.py +++ b/gratipay/models/payment_for_open_source.py @@ -11,7 +11,7 @@ class PaymentForOpenSource(Model): typname = "payments_for_open_source" def __repr__(self): - return ''.format(repr(self.amount)) + return ''.format(repr(self.amount)) @property diff --git a/js/gratipay/homepage.js b/js/gratipay/homepage.js index d07ca9be7e..469c3ad4fd 100644 --- a/js/gratipay/homepage.js +++ b/js/gratipay/homepage.js @@ -3,38 +3,33 @@ Gratipay.homepage = {} Gratipay.homepage.initForm = function(clientAuthorization) { $form = $('#homepage #content form'); - $submit= $form.find('button[type=submit]'); - $submit.click(Gratipay.homepage.submitForm); - - $chooseEcosystem = $form.find('.ecosystem-chooser button'); - $chooseEcosystem.click(function(e) { - e.preventDefault(); - Gratipay.notification('Not implemented.', 'error'); - }); - $promote = $form.find('.promotion-gate button'); $promote.click(Gratipay.homepage.openPromote); + function callback(createErr, instance) { + $submit = $form.find('button[type=submit]'); + $submit.click(function(e) { + e.preventDefault(); + instance.requestPaymentMethod(function(requestPaymentMethodErr, payload) { + Gratipay.homepage.submitFormWithNonce(payload.nonce); + }); + }); + } + braintree.dropin.create({ authorization: clientAuthorization, container: '#braintree-container' - }, function (createErr, instance) { - $submit.click(function () { - instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) { - // Submit payload.nonce to your server - }); - }); - }); -} + }, callback); +}; -Gratipay.homepage.submitForm = function(e) { - e.preventDefault(); - $input = $(this) - $form = $(this).parent('form'); +Gratipay.homepage.submitFormWithNonce = function(nonce) { + $submit = $form.find('button[type=submit]'); + $form = $('#homepage #content form'); var data = new FormData($form[0]); + data.set('payment_method_nonce', nonce); - $input.prop('disable', true); + $submit.prop('disable', true); $.ajax({ url: $form.attr('action'), @@ -43,16 +38,21 @@ Gratipay.homepage.submitForm = function(e) { processData: false, contentType: false, dataType: 'json', - success: function (d) { + success: function(d) { $('a.team_url').attr('href', d.team_url).text(d.team_url); $('a.review_url').attr('href', d.review_url).text(d.review_url); $('form').slideUp(500, function() { $('.application-complete').slideDown(250); }); }, - error: [Gratipay.error, function() { $input.prop('disable', false); }] + error: function(e) { + console.log(e); + debugger; + Gratipay.error(e); + $submit.prop('disable', false); + } }); -} +}; Gratipay.homepage.openPromote = function(e) { e.preventDefault(); @@ -60,4 +60,4 @@ Gratipay.homepage.openPromote = function(e) { $('.promotion-fields').slideDown(function() { $('.promotion-fields input:first').focus(); }); -} +}; diff --git a/tests/py/test_www_homepage.py b/tests/py/test_www_homepage.py index edab4c09ac..3b22c5daba 100644 --- a/tests/py/test_www_homepage.py +++ b/tests/py/test_www_homepage.py @@ -167,7 +167,21 @@ def test_flags_failures_with_transaction_id(self): def test_post_gets_json(self): - response = self.client.POST('/', data=GOOD) + response = self.client.POST('/', data=GOOD, HTTP_ACCEPT=b'application/json') assert response.code == 200 assert response.headers['Content-Type'] == 'application/json' assert json.loads(response.body) == {'parsed': {}, 'errors': []} + + def test_bad_post_gets_400(self): + response = self.client.PxST('/', data=BAD, HTTP_ACCEPT=b'application/json') + assert response.code == 400 + assert response.headers['Content-Type'] == 'application/json' + scrubbed = SCRUBBED.copy() + scrubbed.pop('payment_method_nonce') # consumed + assert json.loads(response.body) == {'parsed': scrubbed, 'errors': ALL} + + def test_really_bad_post_gets_plain_400(self): + response = self.client.PxST('/', data={}, HTTP_ACCEPT=b'application/json') + assert response.code == 400 + assert response.headers == {} + assert response.body == "Missing key: u'amount'" diff --git a/tests/ttw/test_homepage.py b/tests/ttw/test_homepage.py index bcfb1dbe7b..29848ad953 100644 --- a/tests/ttw/test_homepage.py +++ b/tests/ttw/test_homepage.py @@ -5,6 +5,28 @@ class Tests(BrowserHarness): + def fill_form(self, amount, credit_card_number, expiration, cvv, name, email_address, + follow_up, promotion_name, promotion_url, promotion_twitter, promotion_message): + self.wait_for('.braintree-form-number') + self.fill('amount', amount) + with self.get_iframe('braintree-hosted-field-number') as iframe: + iframe.fill('credit-card-number', credit_card_number) + with self.get_iframe('braintree-hosted-field-expirationDate') as iframe: + iframe.fill('expiration', expiration) + with self.get_iframe('braintree-hosted-field-cvv') as iframe: + iframe.fill('cvv', cvv) + self.fill('name', name) + self.fill('email_address', email_address) + if promotion_name: + self.css('.promotion-gate button').type('\n') + # stackoverflow.com/q/11908249#comment58577676_19763087 + self.wait_for('#promotion-message') + self.fill('promotion_name', promotion_name) + self.fill('promotion_url', promotion_url) + self.fill('promotion_twitter', promotion_twitter) + self.fill('promotion_message', promotion_message) + + def test_loads_for_anon(self): assert self.css('#banner h1').html == 'Pay for open source.' assert self.css('#header .sign-in button').html.strip()[:17] == 'Sign in / Sign up' @@ -15,3 +37,18 @@ def test_redirects_for_authed_exclamation_point(self): self.reload() assert self.css('#banner h1').html == 'Browse' assert self.css('.you-are a').html.strip()[:6] == '~alice' + + def test_anon_can_post(self): + self.fill_form('537', '4242424242424242', '1020', '123', 'Alice Liddell', + 'alice@example.com', 'monthly', 'Wonderland', 'http://www.example.com/', + 'thebestbutter', 'Love me! Love me! Say that you love me!') + self.css('fieldset.submit button').type('\n') + import pdb; pdb.set_trace() + assert self.db.one('SELECT * FROM payments_for_open_source').promotion_name == 'Wonderland' + + def est_validation_works(self): + self.fill_form('537', '4242424242424242', '', '', 'Alice Liddell', 'alice@example.com', + 'monthly', 'Wonderland', 'http://www.example.com/', 'thebestbutter', + 'Love me! Love me! Say that you love me!') + self.css('fieldset.submit button').type('\n') + assert self.db.one('select * from moral_license_payments').promotion_name == 'Wonderland' diff --git a/www/index.spt b/www/index.spt index 73015fa217..9d65d86b05 100644 --- a/www/index.spt +++ b/www/index.spt @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import json +from aspen import Response from gratipay.homepage import pay_for_open_source [---] if not user.ANON: @@ -7,6 +9,9 @@ suppress_sidebar = True page_id = "homepage" banner = "Pay for Open Source" result = pay_for_open_source(website.app, request.body) if request.method == 'POST' else {} +if result.get('errors'): + # Hmmm ... bit of an Aspen rough spot ... interaction w/ error.spt, skip it + raise Response(400, json.dumps(result), {'Content-Type': 'application/json'}) [---] application/json via json_dump result [---] text/html @@ -37,7 +42,7 @@ $(document).ready(function() { {% endblock %} {% block content %} -
+
@@ -62,20 +67,21 @@ $(document).ready(function() {
-
- {{ _('Step 2 of 3') }} +
+ {{ _('Optional') }} +

{{ _('Who are you?') }}

- +
@@ -84,23 +90,23 @@ $(document).ready(function() { {{ _('Follow-up') }}
- +
-
- +
- +
@@ -109,19 +115,6 @@ $(document).ready(function() {

-
- -
- {{ _('Optional') }} -

{{ _('Ecosystems') }}

-

- {{ _('Which do you value most?{br}(JavaScript, Python, etc.)', br='
'|safe) }} -

- -
- -
-

{{ _('Promotion') }}

@@ -130,23 +123,24 @@ $(document).ready(function() {

{{ _('Thanks! We are excited to brag about you!') }}

- - + +
- - + +
- - + +
- - + +
@@ -157,18 +151,18 @@ $(document).ready(function() {
-
+
{{ _('Submit Form') }}
-

- {{ _( "By submitting this form you agree to our {0}terms of service{1}." - , ''|safe - , ''|safe + {{ _( "By submitting this form you agree to our {a}terms of service{_a}." + , a=''|safe + , _a=''|safe ) }}