From 87b0555fcf4d46217c1bdd23d6e7b6831e5434e0 Mon Sep 17 00:00:00 2001
From: Chad Whitacre
Date: Sat, 9 Sep 2017 18:51:32 -0400
Subject: [PATCH] HTML/CSS/JavaScript updates
---
js/gratipay/homepage.js | 63 +++++++++--------
scss/elements/buttons-knobs.scss | 2 +-
scss/pages/homepage.scss | 8 +++
tests/py/test_www_homepage.py | 14 +++-
tests/ttw/test_homepage.py | 35 ++++++++++
www/index.spt | 114 ++++++++++++++++++-------------
6 files changed, 154 insertions(+), 82 deletions(-)
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 481a290cfe..62d5dc69f7 100644
--- a/tests/py/test_www_homepage.py
+++ b/tests/py/test_www_homepage.py
@@ -146,8 +146,6 @@ def test_pays_for_open_source(self):
def test_flags_errors_and_doesnt_store(self):
assert self.fetch() is None
result = pay_for_open_source(self.app, self.bad)
- scrubbed = SCRUBBED.copy()
- scrubbed.pop('payment_method_nonce') # consumed
assert result == {'errors': ALL, 'receipt_url': None}
assert self.fetch() is None
@@ -177,3 +175,15 @@ def test_post_gets_json(self):
result = json.loads(response.body)
assert not result['errors']
assert result['receipt_url'].endswith('receipt.html')
+
+ def test_bad_post_gets_400(self):
+ response = self.client.POST('/', data=BAD, HTTP_ACCEPT=b'application/json')
+ assert response.code == 200 # :(
+ assert response.headers['Content-Type'] == 'application/json'
+ assert json.loads(response.body)['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 %}
-
-
-
-
-