diff --git a/js/gratipay/homepage.js b/js/gratipay/homepage.js index d07ca9be7e..9f6ed6b99a 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,20 @@ Gratipay.homepage.submitForm = function(e) { processData: false, contentType: false, dataType: 'json', - 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); }] + success: function(data) { + console.log(data); + // Due to Aspen limitations we use 200 for both success and failure. :/ + if (data.errors.length > 0) { + $submit.prop('disable', false); + Gratipay.notification(data.msg, 'error'); + } else { + $('.payment-complete a.receipt').attr('href', data.receipt_url); + $('.payment-complete').slideDown(200); + $('form').slideUp(500); + } + } }); -} +}; Gratipay.homepage.openPromote = function(e) { e.preventDefault(); @@ -60,4 +59,4 @@ Gratipay.homepage.openPromote = function(e) { $('.promotion-fields').slideDown(function() { $('.promotion-fields input:first').focus(); }); -} +}; diff --git a/scss/elements/buttons-knobs.scss b/scss/elements/buttons-knobs.scss index 843fd1ea78..99738ac997 100644 --- a/scss/elements/buttons-knobs.scss +++ b/scss/elements/buttons-knobs.scss @@ -61,7 +61,7 @@ button.selected:hover:not(:disabled), button.selected.drag, margin: 30px auto 30px; text-align: center; - button.large { + button.large, a.button.large { font: normal 16px/32px $Ideal; padding: 10px 16px; border-radius: 5px; diff --git a/scss/pages/homepage.scss b/scss/pages/homepage.scss index 862fa85007..ad19cd4a29 100644 --- a/scss/pages/homepage.scss +++ b/scss/pages/homepage.scss @@ -183,5 +183,13 @@ display: none; } } + .payment-complete { + .fine-print { + padding: 0; + } + .twitter-container { + padding-top: 20px; + } + } } } diff --git a/tests/py/test_www_homepage.py b/tests/py/test_www_homepage.py index c3b2f51e1d..90326e75ea 100644 --- a/tests/py/test_www_homepage.py +++ b/tests/py/test_www_homepage.py @@ -174,3 +174,17 @@ def test_post_gets_json(self): 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..e57e6d5717 100644 --- a/tests/ttw/test_homepage.py +++ b/tests/ttw/test_homepage.py @@ -5,6 +5,32 @@ class Tests(BrowserHarness): + def fetch(self): + return self.db.one('SELECT pfos.*::payments_for_open_source ' + 'FROM payments_for_open_source pfos') + + 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 +41,12 @@ 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') + self.wait_for('.payment-complete', 10) + assert self.css('.payment-complete .description').text == 'Payment complete.' + assert self.fetch().succeeded diff --git a/www/index.spt b/www/index.spt index 73015fa217..d0d1c95261 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,10 @@ 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 and result['errors']: + # Hmmm ... bit of an Aspen rough spot ... interaction w/ error.spt, skip it + # by overriding 200 for both success and failure. :( + result['msg'] = _("Sorry, we could not process your payment.") [---] application/json via json_dump result [---] text/html @@ -37,7 +43,43 @@ $(document).ready(function() { {% endblock %} {% block content %} -
- - {% endblock %}