Skip to content

Commit 1392891

Browse files
committed
Merge pull request #1761 from rtfd/payment-form
Coalesce implementations of Stripe payment forms
2 parents bb5a50a + f3878bb commit 1392891

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1194
-618
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ whoosh_index
3535
xml_output
3636
public_cnames
3737
public_symlinks
38+
.rope_project/

bower.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"readthedocs-client": "https://github.com/agjohnson/readthedocs-client-js.git",
2020
"sphinx-rtd-theme": "https://github.com/snide/sphinx-rtd-theme.git#0.1.9",
2121
"knockout": "~3.3.0",
22-
"jquery.payment": "~1.2.3",
22+
"jquery.payment": "~1.3.0",
2323
"jquery-migrate": "~1.2.1",
2424
"jquery-ui": "1.8.23"
2525
},

media/css/core.css

+16-16
Original file line numberDiff line numberDiff line change
@@ -836,27 +836,27 @@ ul.donate-supporters.donate-supporters-large div.supporter-name {
836836
font-size: .9em;
837837
}
838838

839-
div#payment-form div.cc-type {
840-
height: 23px;
841-
margin: 3px 0px 10px;
842-
background: url('/static/donate/img/creditcard.png');
843-
background-repeat: no-repeat;
844-
}
845-
div#payment-form input#cc-number.visa + div.cc-type {
846-
background-position: 0px -23px;
839+
/* Gold */
840+
div.gold-subscription p.subscription-detail,
841+
div.gold-subscription p.subscription-projects {
842+
margin: 0em;
847843
}
848-
div#payment-form input#cc-number.mastercard + div.cc-type {
849-
background-position: 0px -46px;
844+
845+
div.gold-subscription p.subscription-detail label {
846+
display: inline-block;
850847
}
851-
div#payment-form input#cc-number.amex + div.cc-type {
852-
background-position: 0px -69px;
848+
849+
div.gold-subscription p.subscription-detail-card > span {
850+
font-family: monospace;
853851
}
854-
div#payment-form input#cc-number.discover + div.cc-type {
855-
background-position: 0px -92px;
852+
853+
div.gold-subscription > form {
854+
display: inline-block;
856855
}
857856

858-
div#payment-form input#cc-expiry { width: 150px; }
859-
div#payment-form input#cc-cvv { width: 100px; }
857+
div.gold-subscription > form button {
858+
margin: 1em .3em 1.5em 0em;
859+
}
860860

861861
/* Form Wizards */
862862
div.actions.wizard-actions button.action-primary,

readthedocs/core/mixins.py

+9-14
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,9 @@
22
Common mixin classes for views
33
"""
44

5-
from django.conf import settings
6-
75
from vanilla import ListView
8-
9-
10-
class StripeMixin(object):
11-
12-
"""Adds Stripe publishable key to the context data"""
13-
14-
def get_context_data(self, **kwargs):
15-
context = super(StripeMixin, self).get_context_data(**kwargs)
16-
context.update({
17-
'publishable': settings.STRIPE_PUBLISHABLE,
18-
})
19-
return context
6+
from django.contrib.auth.decorators import login_required
7+
from django.utils.decorators import method_decorator
208

219

2210
class ListViewWithForm(ListView):
@@ -27,3 +15,10 @@ def get_context_data(self, **kwargs):
2715
context = super(ListViewWithForm, self).get_context_data(**kwargs)
2816
context['form'] = self.get_form(data=None, files=None)
2917
return context
18+
19+
20+
class LoginRequiredMixin(object):
21+
22+
@method_decorator(login_required)
23+
def dispatch(self, *args, **kwargs):
24+
return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)

readthedocs/core/static-src/core/js/payment.js

-99
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{% if field.is_hidden %}
2+
{{ field }}
3+
{% else %}
4+
<!-- Begin: {{ field.name }} -->
5+
{{ field.errors }}
6+
{% if 'data-bind' in field.field.widget.attrs %}
7+
<ul
8+
class="errorlist"
9+
data-bind="visible: error_{{ field.name }}"
10+
style="display: none;">
11+
<li data-bind="text: error_{{ field.name }}"></li>
12+
</ul>
13+
{% endif %}
14+
<p>
15+
<label for="{{ field.id_for_label }}">{{ field.label }}:</label>
16+
{{ field }}
17+
{% if field.help_text %}
18+
<span class="helptext">{{ field.help_text }}</span>
19+
{% endif %}
20+
</p>
21+
<!-- End: {{ field.name }} -->
22+
{% endif %}

readthedocs/donate/forms.py

+27-23
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1+
"""Forms for RTD donations"""
2+
13
import logging
24

35
from django import forms
46
from django.conf import settings
57
from django.utils.translation import ugettext_lazy as _
68

7-
import stripe
9+
from readthedocs.payments.forms import StripeModelForm, StripeResourceMixin
10+
from readthedocs.payments.utils import stripe
811

912
from .models import Supporter
1013

1114
log = logging.getLogger(__name__)
1215

1316

14-
class SupporterForm(forms.ModelForm):
17+
class SupporterForm(StripeResourceMixin, StripeModelForm):
18+
19+
"""Donation support sign up form
20+
21+
This extends the basic payment form, giving fields for credit card number,
22+
expiry, and CVV. The proper Knockout data bindings are established on
23+
:py:cls:`StripeModelForm`
24+
"""
1525

1626
class Meta:
1727
model = Supporter
1828
fields = (
1929
'last_4_digits',
20-
'stripe_id',
2130
'name',
2231
'email',
2332
'dollars',
@@ -43,38 +52,33 @@ class Meta:
4352
}),
4453
'site_url': forms.TextInput(attrs={
4554
'data-bind': 'value: site_url, enable: urls_enabled'
46-
})
55+
}),
56+
'last_4_digits': forms.TextInput(attrs={
57+
'data-bind': 'valueInit: card_digits, value: card_digits'
58+
}),
4759
}
4860

4961
last_4_digits = forms.CharField(widget=forms.HiddenInput(), required=True)
50-
stripe_id = forms.CharField(widget=forms.HiddenInput(), required=True)
62+
name = forms.CharField(required=True)
63+
email = forms.CharField(required=True)
5164

5265
def __init__(self, *args, **kwargs):
5366
self.user = kwargs.pop('user')
5467
super(SupporterForm, self).__init__(*args, **kwargs)
5568

56-
def clean(self):
57-
'''Call stripe for payment (not ideal here) and clean up logo < $200'''
69+
def validate_stripe(self):
70+
"""Call stripe for payment (not ideal here) and clean up logo < $200"""
5871
dollars = self.cleaned_data['dollars']
5972
if dollars < 200:
6073
self.cleaned_data['logo_url'] = None
6174
self.cleaned_data['site_url'] = None
62-
try:
63-
stripe.api_key = settings.STRIPE_SECRET
64-
stripe.Charge.create(
65-
amount=int(self.cleaned_data['dollars']) * 100,
66-
currency='usd',
67-
source=self.cleaned_data['stripe_id'],
68-
description='Read the Docs Sustained Engineering',
69-
receipt_email=self.cleaned_data['email']
70-
)
71-
except stripe.error.CardError, e:
72-
stripe_error = e.json_body['error']
73-
log.error('Credit card error: %s', stripe_error['message'])
74-
raise forms.ValidationError(
75-
_('There was a problem processing your card: %(message)s'),
76-
params=stripe_error)
77-
return self.cleaned_data
75+
stripe.Charge.create(
76+
amount=int(self.cleaned_data['dollars']) * 100,
77+
currency='usd',
78+
source=self.cleaned_data['stripe_token'],
79+
description='Read the Docs Sustained Engineering',
80+
receipt_email=self.cleaned_data['email']
81+
)
7882

7983
def save(self, commit=True):
8084
supporter = super(SupporterForm, self).save(commit)

readthedocs/donate/mixins.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
'''
2-
Mixin classes for donation views
3-
'''
1+
"""Mixin classes for donation views"""
42

53
from django.db.models import Avg, Sum
64

75
from .models import Supporter
86

97

108
class DonateProgressMixin(object):
11-
'''Add donation progress to context data'''
129

13-
def get_context_data(self):
14-
context = super(DonateProgressMixin, self).get_context_data()
10+
"""Add donation progress to context data"""
11+
12+
def get_context_data(self, **kwargs):
13+
context = super(DonateProgressMixin, self).get_context_data(**kwargs)
1514
sums = (Supporter.objects
1615
.aggregate(dollars=Sum('dollars')))
1716
avgs = (Supporter.objects

readthedocs/donate/static-src/donate/js/donate.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
// Donate payment views
22

33
var jquery = require('jquery'),
4-
payment = require('../../../../core/static-src/core/js/payment'),
4+
payment = require('readthedocs/payments/static-src/payments/js/base'),
55
ko = require('knockout');
66

77
function DonateView (config) {
88
var self = this,
99
config = config || {};
1010

11-
ko.utils.extend(self, new payment.PaymentView(config));
11+
self.constructor.call(self, config);
1212

1313
self.dollars = ko.observable();
1414
self.logo_url = ko.observable();
1515
self.site_url = ko.observable();
16+
self.error_dollars = ko.observable();
17+
self.error_logo_url = ko.observable();
18+
self.error_site_url = ko.observable();
19+
1620
ko.computed(function () {
1721
var input_logo = $('input#id_logo_url').closest('p'),
1822
input_site = $('input#id_site_url').closest('p');
@@ -32,6 +36,8 @@ function DonateView (config) {
3236
});
3337
}
3438

39+
DonateView.prototype = new payment.PaymentView();
40+
3541
DonateView.init = function (config, obj) {
3642
var view = new DonateView(config),
3743
obj = obj || $('#donate-payment')[0];
Loading

0 commit comments

Comments
 (0)