mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Merge branch 'master' into social
* master: Changing copy on premium/feedchooser dialog. Styling errors on stripe payment form. Final stripe.js tweaks before launch. Adding Pay by Credit Card as an option to feed chooser dialog. Thus concludes Stripe.js integration. Time to launch! Styling the stripe.js background. Using correct kwargs for stripe signal. Wrapping up stripe.js form. Has validation, styling, and does the right thing for new subscriptions. Needs a link to the form, though. Setting up correct customer model for stripe webhook. Moving JS assets to bottom of the page for faster loadtimes. Fixing exception on missing param in feed address searching. Fixing recommendation date serialization bug. Fixing bugs around login with blank password using full password. Also fixing bug in signups with no username. Stripe.js payments using zebra. Adding zebra as a vendored dependency. Webhooks and views all in. Needs styling, custom username and email fields, and loads of testing. Adding error checking on requests in page fetching. Using a probability from redis to determine whether or not to skip a fetch. Allowing any password to be used on accounts with no password set. Adding paypal logo. Conflicts: assets.yml media/js/newsblur/reader/reader_feedchooser.js settings.py templates/base.html
This commit is contained in:
commit
aaa5aa1cd6
51 changed files with 1331 additions and 339 deletions
33
apps/profile/forms.py
Normal file
33
apps/profile/forms.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from django import forms
|
||||
from vendor.zebra.forms import StripePaymentForm
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
PLANS = [
|
||||
("newsblur-premium-12", mark_safe("$12 / year <span class='NB-small'>($1/month)</span>")),
|
||||
("newsblur-premium-24", mark_safe("$24 / year <span class='NB-small'>($2/month)</span>")),
|
||||
("newsblur-premium-36", mark_safe("$36 / year <span class='NB-small'>($3/month)</span>")),
|
||||
]
|
||||
|
||||
class HorizRadioRenderer(forms.RadioSelect.renderer):
|
||||
""" this overrides widget method to put radio buttons horizontally
|
||||
instead of vertically.
|
||||
"""
|
||||
def render(self):
|
||||
"""Outputs radios"""
|
||||
choices = '\n'.join(['%s\n' % w for w in self])
|
||||
return mark_safe('<div class="NB-stripe-plan-choice">%s</div>' % choices)
|
||||
|
||||
class StripePlusPaymentForm(StripePaymentForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
email = kwargs.pop('email')
|
||||
plan = kwargs.pop('plan', '')
|
||||
super(StripePlusPaymentForm, self).__init__(*args, **kwargs)
|
||||
self.fields['email'].initial = email
|
||||
if plan:
|
||||
self.fields['plan'].initial = plan
|
||||
|
||||
email = forms.EmailField(widget=forms.TextInput(attrs=dict(maxlength=75)),
|
||||
label='Email address',
|
||||
required=False)
|
||||
plan = forms.ChoiceField(required=False, widget=forms.RadioSelect(renderer=HorizRadioRenderer),
|
||||
choices=PLANS, label='Plan')
|
85
apps/profile/migrations/0016_profile_stripe.py
Normal file
85
apps/profile/migrations/0016_profile_stripe.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding field 'Profile.stripe_4_digits'
|
||||
db.add_column('profile_profile', 'stripe_4_digits', self.gf('django.db.models.fields.CharField')(max_length=4, null=True, blank=True), keep_default=False)
|
||||
|
||||
# Adding field 'Profile.stripe_id'
|
||||
db.add_column('profile_profile', 'stripe_id', self.gf('django.db.models.fields.CharField')(max_length=24, null=True, blank=True), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting field 'Profile.stripe_4_digits'
|
||||
db.delete_column('profile_profile', 'stripe_4_digits')
|
||||
|
||||
# Deleting field 'Profile.stripe_id'
|
||||
db.delete_column('profile_profile', 'stripe_id')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'profile.profile': {
|
||||
'Meta': {'object_name': 'Profile'},
|
||||
'collapsed_folders': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
|
||||
'feed_pane_size': ('django.db.models.fields.IntegerField', [], {'default': '240'}),
|
||||
'hide_mobile': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_premium': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_seen_ip': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'last_seen_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'preferences': ('django.db.models.fields.TextField', [], {'default': "'{}'"}),
|
||||
'secret_token': ('django.db.models.fields.CharField', [], {'max_length': '12', 'null': 'True', 'blank': 'True'}),
|
||||
'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'stripe_4_digits': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
|
||||
'stripe_id': ('django.db.models.fields.CharField', [], {'max_length': '24', 'null': 'True', 'blank': 'True'}),
|
||||
'timezone': ('vendor.timezones.fields.TimeZoneField', [], {'default': "'America/New_York'", 'max_length': '100'}),
|
||||
'tutorial_finished': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
|
||||
'view_settings': ('django.db.models.fields.TextField', [], {'default': "'{}'"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['profile']
|
|
@ -18,7 +18,7 @@ from utils import log as logging
|
|||
from utils.user_functions import generate_secret_token
|
||||
from vendor.timezones.fields import TimeZoneField
|
||||
from vendor.paypal.standard.ipn.signals import subscription_signup
|
||||
|
||||
from zebra.signals import zebra_webhook_customer_subscription_created
|
||||
|
||||
class Profile(models.Model):
|
||||
user = models.OneToOneField(User, unique=True, related_name="profile")
|
||||
|
@ -37,6 +37,8 @@ class Profile(models.Model):
|
|||
last_seen_ip = models.CharField(max_length=50, blank=True, null=True)
|
||||
timezone = TimeZoneField(default="America/New_York")
|
||||
secret_token = models.CharField(max_length=12, blank=True, null=True)
|
||||
stripe_4_digits = models.CharField(max_length=4, blank=True, null=True)
|
||||
stripe_id = models.CharField(max_length=24, blank=True, null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s <%s> (Premium: %s)" % (self.user, self.user.email, self.is_premium)
|
||||
|
@ -190,6 +192,11 @@ def paypal_signup(sender, **kwargs):
|
|||
user.profile.activate_premium()
|
||||
subscription_signup.connect(paypal_signup)
|
||||
|
||||
def stripe_signup(sender, full_json, **kwargs):
|
||||
profile = Profile.objects.get(stripe_id=full_json['data']['object']['customer'])
|
||||
profile.activate_premium()
|
||||
zebra_webhook_customer_subscription_created.connect(stripe_signup)
|
||||
|
||||
def change_password(user, old_password, new_password):
|
||||
user_db = authenticate(username=user.username, password=old_password)
|
||||
if user_db is None:
|
||||
|
|
|
@ -12,4 +12,5 @@ urlpatterns = patterns('',
|
|||
url(r'^paypal_return/?', views.paypal_return, name='paypal-return'),
|
||||
url(r'^is_premium/?', views.profile_is_premium, name='profile-is-premium'),
|
||||
url(r'^paypal_ipn/?', include('paypal.standard.ipn.urls'), name='paypal-ipn'),
|
||||
url(r'^stripe_form/?', views.stripe_form, name='stripe-form'),
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import stripe
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
|
@ -7,11 +8,13 @@ from django.core.urlresolvers import reverse
|
|||
from django.template import RequestContext
|
||||
from django.shortcuts import render_to_response
|
||||
from django.core.mail import mail_admins
|
||||
from utils import json_functions as json
|
||||
from vendor.paypal.standard.forms import PayPalPaymentsForm
|
||||
from utils.user_functions import ajax_login_required
|
||||
from django.conf import settings
|
||||
from apps.profile.models import Profile, change_password
|
||||
from apps.reader.models import UserSubscription
|
||||
from apps.profile.forms import StripePlusPaymentForm, PLANS
|
||||
from utils import json_functions as json
|
||||
from utils.user_functions import ajax_login_required
|
||||
from vendor.paypal.standard.forms import PayPalPaymentsForm
|
||||
|
||||
SINGLE_FIELD_PREFS = ('timezone','feed_pane_size','tutorial_finished','hide_mobile','send_emails',
|
||||
'has_trained_intelligence', 'hide_find_friends', 'hide_getting_started',)
|
||||
|
@ -61,28 +64,28 @@ def get_preference(request):
|
|||
def set_account_settings(request):
|
||||
code = 1
|
||||
message = ''
|
||||
settings = request.POST
|
||||
post_settings = request.POST
|
||||
|
||||
if settings['username'] and request.user.username != settings['username']:
|
||||
if post_settings['username'] and request.user.username != post_settings['username']:
|
||||
try:
|
||||
User.objects.get(username__iexact=settings['username'])
|
||||
User.objects.get(username__iexact=post_settings['username'])
|
||||
except User.DoesNotExist:
|
||||
request.user.username = settings['username']
|
||||
request.user.username = post_settings['username']
|
||||
request.user.save()
|
||||
else:
|
||||
code = -1
|
||||
message = "This username is already taken. Try something different."
|
||||
|
||||
if request.user.email != settings['email']:
|
||||
if not User.objects.filter(email=settings['email']).count():
|
||||
request.user.email = settings['email']
|
||||
if request.user.email != post_settings['email']:
|
||||
if not User.objects.filter(email=post_settings['email']).count():
|
||||
request.user.email = post_settings['email']
|
||||
request.user.save()
|
||||
else:
|
||||
code = -2
|
||||
message = "This email is already being used by another account. Try something different."
|
||||
|
||||
if code != -1 and (settings['old_password'] or settings['new_password']):
|
||||
code = change_password(request.user, settings['old_password'], settings['new_password'])
|
||||
if code != -1 and (post_settings['old_password'] or post_settings['new_password']):
|
||||
code = change_password(request.user, post_settings['old_password'], post_settings['new_password'])
|
||||
if code == -3:
|
||||
message = "Your old password is incorrect."
|
||||
|
||||
|
@ -193,4 +196,46 @@ def profile_is_premium(request):
|
|||
'activated_subs': activated_subs,
|
||||
'total_subs': total_subs,
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
def stripe_form(request):
|
||||
user = request.user
|
||||
success_updating = False
|
||||
stripe.api_key = settings.STRIPE_SECRET
|
||||
plan = int(request.GET.get('plan', 2))
|
||||
plan = PLANS[plan-1][0]
|
||||
|
||||
if request.method == 'POST':
|
||||
zebra_form = StripePlusPaymentForm(request.POST, email=user.email)
|
||||
if zebra_form.is_valid():
|
||||
user.email = zebra_form.cleaned_data['email']
|
||||
user.save()
|
||||
|
||||
customer = stripe.Customer.create(**{
|
||||
'card': zebra_form.cleaned_data['stripe_token'],
|
||||
'plan': zebra_form.cleaned_data['plan'],
|
||||
'email': user.email,
|
||||
'description': user.username,
|
||||
})
|
||||
|
||||
user.profile.strip_4_digits = zebra_form.cleaned_data['last_4_digits']
|
||||
user.profile.stripe_id = customer.id
|
||||
user.profile.save()
|
||||
|
||||
success_updating = True
|
||||
|
||||
else:
|
||||
zebra_form = StripePlusPaymentForm(email=user.email, plan=plan)
|
||||
|
||||
if success_updating:
|
||||
return render_to_response('reader/paypal_return.xhtml',
|
||||
{}, context_instance=RequestContext(request))
|
||||
|
||||
return render_to_response('profile/stripe_form.xhtml',
|
||||
{
|
||||
'zebra_form': zebra_form,
|
||||
'publishable': settings.STRIPE_PUBLISHABLE,
|
||||
'success_updating': success_updating,
|
||||
},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
|
|
|
@ -27,25 +27,29 @@ class LoginForm(forms.Form):
|
|||
user = User.objects.filter(Q(username__iexact=username) | Q(email=username))
|
||||
if username and user:
|
||||
self.user_cache = authenticate(username=user[0].username, password=password)
|
||||
if self.user_cache is None:
|
||||
self.user_cache = authenticate(username=user[0].username, password="")
|
||||
if self.user_cache is None:
|
||||
email_username = User.objects.filter(email=username)
|
||||
if email_username:
|
||||
self.user_cache = authenticate(username=email_username[0].username, password=password)
|
||||
if self.user_cache is None:
|
||||
# logging.info(" ***> [%s] Bad Login: TRYING JK-LESS PASSWORD" % username)
|
||||
jkless_password = password.replace('j', '').replace('k', '')
|
||||
self.user_cache = authenticate(username=username, password=jkless_password)
|
||||
if self.user_cache is None:
|
||||
logging.info(" ***> [%s] Bad Login" % username)
|
||||
raise forms.ValidationError(_("Whoopsy-daisy. Try again."))
|
||||
else:
|
||||
# Supreme fuck-up. Accidentally removed the letters J and K from
|
||||
# all user passwords. Re-save with correct password.
|
||||
logging.info(" ***> [%s] FIXING JK-LESS PASSWORD" % username)
|
||||
self.user_cache.set_password(password)
|
||||
self.user_cache.save()
|
||||
elif not self.user_cache.is_active:
|
||||
raise forms.ValidationError(_("This account is inactive."))
|
||||
self.user_cache = authenticate(username=email_username[0].username, password="")
|
||||
if self.user_cache is None:
|
||||
# logging.info(" ***> [%s] Bad Login: TRYING JK-LESS PASSWORD" % username)
|
||||
jkless_password = password.replace('j', '').replace('k', '')
|
||||
self.user_cache = authenticate(username=username, password=jkless_password)
|
||||
if self.user_cache is None:
|
||||
logging.info(" ***> [%s] Bad Login" % username)
|
||||
raise forms.ValidationError(_("Whoopsy-daisy. Try again."))
|
||||
else:
|
||||
# Supreme fuck-up. Accidentally removed the letters J and K from
|
||||
# all user passwords. Re-save with correct password.
|
||||
logging.info(" ***> [%s] FIXING JK-LESS PASSWORD" % username)
|
||||
self.user_cache.set_password(password)
|
||||
self.user_cache.save()
|
||||
if not self.user_cache.is_active:
|
||||
raise forms.ValidationError(_("This account is inactive."))
|
||||
elif username and not user:
|
||||
raise forms.ValidationError(_("That username is not registered. Create an account with it instead."))
|
||||
|
||||
|
@ -93,8 +97,8 @@ class SignupForm(forms.Form):
|
|||
return self.cleaned_data['email']
|
||||
|
||||
def clean(self):
|
||||
username = self.cleaned_data['username']
|
||||
password = self.cleaned_data['password']
|
||||
username = self.cleaned_data.get('username', '')
|
||||
password = self.cleaned_data.get('password', '')
|
||||
exists = User.objects.filter(username__iexact=username).count()
|
||||
if exists:
|
||||
user_auth = authenticate(username=username, password=password)
|
||||
|
|
|
@ -88,6 +88,7 @@ class PageImporter(object):
|
|||
LookupError,
|
||||
requests.packages.urllib3.exceptions.HTTPError), e:
|
||||
logging.debug(' ***> [%-30s] Page fetch failed using requests: %s' % (self.feed, e))
|
||||
mail_feed_error_to_admin(self.feed, e, locals())
|
||||
return self.fetch_page(urllib_fallback=True, requests_exception=e)
|
||||
except Exception, e:
|
||||
logging.debug('[%d] ! -------------------------' % (self.feed.id,))
|
||||
|
|
|
@ -13,7 +13,7 @@ class UpdateFeeds(Task):
|
|||
|
||||
options = {
|
||||
'fake': bool(MStatistics.get('fake_fetch')),
|
||||
'quick': bool(MStatistics.get('quick_fetch')),
|
||||
'quick': float(MStatistics.get('quick_fetch', 0)),
|
||||
}
|
||||
|
||||
if not isinstance(feed_pks, list):
|
||||
|
|
|
@ -20,8 +20,11 @@ from utils.view_functions import get_argument_or_404
|
|||
|
||||
@json.json_view
|
||||
def search_feed(request):
|
||||
address = request.REQUEST['address']
|
||||
address = request.REQUEST.get('address')
|
||||
offset = int(request.REQUEST.get('offset', 0))
|
||||
if not address:
|
||||
return dict(code=-1, message="Please provide a URL/address.")
|
||||
|
||||
feed = Feed.get_feed_from_url(address, create=False, aggressive=True, offset=offset)
|
||||
|
||||
if feed:
|
||||
|
|
|
@ -76,8 +76,9 @@ javascripts:
|
|||
- media/js/newsblur/models/*.js
|
||||
- media/js/newsblur/common/assetmodel.js
|
||||
- media/js/mobile/newsblur/mobile_workspace.js
|
||||
paypal:
|
||||
- media/js/newsblur/paypal/paypal_return.js
|
||||
payments:
|
||||
- media/js/newsblur/payments/paypal_return.js
|
||||
- media/js/newsblur/payments/stripe_form.js
|
||||
bookmarklet:
|
||||
- media/js/vendor/jquery-1.5.1.min.js
|
||||
- media/js/vendor/jquery.noConflict.js
|
||||
|
|
8
fabfile.py
vendored
8
fabfile.py
vendored
|
@ -71,7 +71,7 @@ def pull():
|
|||
run('git pull')
|
||||
|
||||
def pre_deploy():
|
||||
compress_assets()
|
||||
compress_assets(bundle=True)
|
||||
|
||||
def post_deploy():
|
||||
cleanup_assets()
|
||||
|
@ -134,12 +134,14 @@ def celery():
|
|||
celery_stop()
|
||||
celery_start()
|
||||
|
||||
@parallel
|
||||
def celery_stop():
|
||||
with cd(env.NEWSBLUR_PATH):
|
||||
run('sudo supervisorctl stop celery')
|
||||
with settings(warn_only=True):
|
||||
run('./utils/kill_celery.sh')
|
||||
|
||||
@parallel
|
||||
def celery_start():
|
||||
with cd(env.NEWSBLUR_PATH):
|
||||
run('sudo supervisorctl start celery')
|
||||
|
@ -149,7 +151,7 @@ def kill_celery():
|
|||
with cd(env.NEWSBLUR_PATH):
|
||||
run('ps aux | grep celeryd | egrep -v grep | awk \'{print $2}\' | sudo xargs kill -9')
|
||||
|
||||
def compress_assets():
|
||||
def compress_assets(bundle=False):
|
||||
local('jammit -c assets.yml --base-url http://www.newsblur.com --output static')
|
||||
local('tar -czf static.tgz static/*')
|
||||
|
||||
|
@ -310,7 +312,7 @@ def setup_psycopg():
|
|||
|
||||
def setup_python():
|
||||
sudo('easy_install -U pip')
|
||||
sudo('easy_install -U fabric django readline pyflakes iconv celery django-celery django-celery-with-redis django-compress South django-extensions pymongo BeautifulSoup pyyaml nltk==0.9.9 lxml oauth2 pytz boto seacucumber django_ses mongoengine redis requests')
|
||||
sudo('easy_install -U fabric django readline pyflakes iconv celery django-celery django-celery-with-redis django-compress South django-extensions pymongo stripe BeautifulSoup pyyaml nltk==0.9.9 lxml oauth2 pytz boto seacucumber django_ses mongoengine redis requests')
|
||||
|
||||
put('config/pystartup.py', '.pystartup')
|
||||
with cd(os.path.join(env.NEWSBLUR_PATH, 'vendor/cjson')):
|
||||
|
|
|
@ -38,6 +38,9 @@ S3_ACCESS_KEY = 'XXX'
|
|||
S3_SECRET = 'SECRET'
|
||||
S3_BACKUP_BUCKET = 'newsblur_backups'
|
||||
|
||||
STRIPE_SECRET = "YOUR-SECRET-API-KEY"
|
||||
STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY"
|
||||
|
||||
# =============
|
||||
# = Databases =
|
||||
# =============
|
||||
|
|
154
media/css/payments.css
Normal file
154
media/css/payments.css
Normal file
|
@ -0,0 +1,154 @@
|
|||
/* ========== */
|
||||
/* = Paypal = */
|
||||
/* ========== */
|
||||
|
||||
.NB-paypal-return {
|
||||
margin: 176px 0 0;
|
||||
background-color: #D3E7BA;
|
||||
border-top: 1px solid #A0A0A0;
|
||||
border-bottom: 1px solid #A0A0A0;
|
||||
padding: 24px 0;
|
||||
background-image: linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
background-image: -moz-linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
background-image: -ms-linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.NB-paypal-return .NB-paypal-return-title {
|
||||
font-size: 36px;
|
||||
margin: 0 0 12px;
|
||||
color: #303030;
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.NB-paypal-return .NB-paypal-return-subtitle {
|
||||
font-size: 24px;
|
||||
color: #324A15;
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
}
|
||||
|
||||
.NB-paypal-return .NB-paypal-return-loading {
|
||||
margin: 18px auto 0;
|
||||
height: 16px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* = Stripe = */
|
||||
/* ========== */
|
||||
|
||||
.NB-stripe-form-wrapper {
|
||||
margin: 56px 0 18px;
|
||||
background-color: #D3E7BA;
|
||||
border-top: 1px solid #A0A0A0;
|
||||
border-bottom: 1px solid #A0A0A0;
|
||||
padding: 24px 0;
|
||||
background-image: linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
background-image: -moz-linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
background-image: -ms-linear-gradient(bottom, rgb(188,214,167) 0%, rgb(223,247,212) 100%);
|
||||
}
|
||||
.NB-stripe-form {
|
||||
margin: 0 auto;
|
||||
width: 360px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.NB-stripe-form input,
|
||||
.NB-stripe-form select {
|
||||
margin: 6px 0 2px;
|
||||
width: 200px;
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
border: 1px solid #606060;
|
||||
-moz-box-shadow:2px 2px 0 #A0B998;
|
||||
-webkit-box-shadow:2px 2px 0 #A0B998;
|
||||
box-shadow:2px 2px 0 #A0B998;
|
||||
}
|
||||
|
||||
.NB-stripe-form input.error,
|
||||
.NB-stripe-form select.error {
|
||||
border-color: #830C0C;
|
||||
}
|
||||
.NB-stripe-form button {
|
||||
width: 200px;
|
||||
margin: 12px 0 4px 150px;
|
||||
|
||||
-moz-box-shadow:2px 2px 0 #A0B998;
|
||||
-webkit-box-shadow:2px 2px 0 #A0B998;
|
||||
box-shadow:2px 2px 0 #A0B998;
|
||||
}
|
||||
.NB-stripe-form .helptext {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.NB-stripe-form #id_card_cvv {
|
||||
width: 42px;
|
||||
}
|
||||
|
||||
.NB-stripe-form .NB-stripe-username {
|
||||
margin: 6px 0 12px;
|
||||
}
|
||||
|
||||
.NB-stripe-form label {
|
||||
width: 150px;
|
||||
display: block;
|
||||
float: left;
|
||||
clear: both;
|
||||
margin: 6px 0 0;
|
||||
padding: 2px 0 0;
|
||||
text-shadow: 0 1px 0 #F3FFED;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.NB-stripe-form .NB-creditcards {
|
||||
margin: 8px 0 0 150px;
|
||||
}
|
||||
|
||||
.NB-stripe-form input[type=submit] {
|
||||
margin-left: 150px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.NB-stripe-form label.error {
|
||||
width: 200px;
|
||||
margin-left: 150px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.NB-stripe-form p {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.NB-stripe-form .NB-stripe-plan-choice {
|
||||
float: left;
|
||||
width: 200px;
|
||||
margin-top: 4px;
|
||||
padding: 0 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.NB-stripe-form .NB-stripe-plan-choice label {
|
||||
width: auto;
|
||||
float: left;
|
||||
margin-top: 0;
|
||||
}
|
||||
.NB-stripe-form .NB-stripe-plan-choice input {
|
||||
width: auto;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.NB-stripe-form .NB-small {
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
color: #575857;
|
||||
}
|
||||
.NB-stripe-form .payment-errors {
|
||||
margin: 8px 0 0 150px;
|
||||
color: #600000;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
.NB-paypal-return {
|
||||
margin: 176px 0 0;
|
||||
background-color: #CBE5C7;
|
||||
border-top: 1px solid #A0A0A0;
|
||||
border-bottom: 1px solid #A0A0A0;
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.NB-paypal-return .NB-paypal-return-title {
|
||||
font-size: 36px;
|
||||
margin: 0 0 12px;
|
||||
color: #303030;
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.NB-paypal-return .NB-paypal-return-subtitle {
|
||||
font-size: 24px;
|
||||
color: #324A15;
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
}
|
||||
|
||||
.NB-paypal-return .NB-paypal-return-loading {
|
||||
margin: 18px auto 0;
|
||||
height: 16px;
|
||||
width: 300px;
|
||||
}
|
|
@ -5447,7 +5447,7 @@ background: transparent;
|
|||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
color: #427700;
|
||||
margin: 24px 0 1px;
|
||||
margin: 12px 0 1px;
|
||||
font-weight: bold;
|
||||
text-shadow:1px 1px 0 #F0F0F0;
|
||||
}
|
||||
|
@ -5455,7 +5455,7 @@ background: transparent;
|
|||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #C0C0C0;
|
||||
margin: 24px 0 0;
|
||||
margin: 12px 0 0;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
text-shadow:1px 1px 0 #F6F6F6;
|
||||
|
@ -5469,6 +5469,9 @@ background: transparent;
|
|||
color: #C05050;
|
||||
}
|
||||
|
||||
.NB-modal-feedchooser .NB-modal-subtitle {
|
||||
width: auto;
|
||||
}
|
||||
.NB-modal-feedchooser .NB-feedchooser {
|
||||
background-color: #D7DDE6;
|
||||
overflow-y: auto;
|
||||
|
@ -5503,16 +5506,38 @@ background: transparent;
|
|||
|
||||
.NB-modal-feedchooser .NB-feedchooser-paypal {
|
||||
min-height: 48px;
|
||||
width: 135px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
margin-top: 16px;
|
||||
float: left;
|
||||
clear: both;
|
||||
padding: 16px 0 0;
|
||||
}
|
||||
|
||||
.NB-modal-feedchooser .NB-feedchooser-dollar {
|
||||
.NB-modal-feedchooser .NB-feedchooser-paypal img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.NB-modal-feedchooser .NB-feedchooser-stripe {
|
||||
min-height: 48px;
|
||||
width: 44%;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
margin: 0px 0px 0px 0;
|
||||
margin: 12px 0 8px;
|
||||
padding-left: 12px;
|
||||
border-left: 1px solid #C6B400;
|
||||
}
|
||||
|
||||
.NB-modal-feedchooser .NB-feedchooser-stripe .NB-modal-submit-green {
|
||||
-moz-box-shadow:2px 2px 0 #E2D121;
|
||||
box-shadow:2px 2px 0 #E2D121;
|
||||
}
|
||||
.NB-modal-feedchooser .NB-creditcards img {
|
||||
width: 28px;
|
||||
margin: 0 2px 0 0;
|
||||
}
|
||||
.NB-modal-feedchooser .NB-feedchooser-dollar {
|
||||
margin: 0px auto;
|
||||
padding: 4px 0 4px 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -5537,7 +5562,7 @@ background: transparent;
|
|||
width: 40px;
|
||||
height: 40px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
left: -40px;
|
||||
}
|
||||
.NB-modal-feedchooser .NB-feedchooser-dollar-value.NB-selected.NB-1 .NB-feedchooser-dollar-image {
|
||||
top: -8px;
|
||||
|
@ -5559,6 +5584,7 @@ background: transparent;
|
|||
display: inline;
|
||||
padding: 0 4px 0 0;
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.NB-modal-feedchooser .NB-selected .NB-feedchooser-dollar-month {
|
||||
|
@ -6946,4 +6972,4 @@ background: transparent;
|
|||
.NB-modal-profile .NB-profile-actions {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
|
BIN
media/img/reader/logo-paypal.png
Normal file
BIN
media/img/reader/logo-paypal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -1,46 +0,0 @@
|
|||
(function($) {
|
||||
|
||||
$(document).ready(function() {
|
||||
NEWSBLUR.paypal_return = new NEWSBLUR.PaypalReturn();
|
||||
});
|
||||
|
||||
NEWSBLUR.PaypalReturn = function() {
|
||||
this.retries = 0;
|
||||
this.detect_premium();
|
||||
setInterval(_.bind(function() { this.detect_premium(); }, this), 1500);
|
||||
};
|
||||
|
||||
NEWSBLUR.PaypalReturn.prototype = {
|
||||
|
||||
detect_premium: function() {
|
||||
$.ajax({
|
||||
'url' : '/profile/is_premium',
|
||||
'data' : {'retries': this.retries},
|
||||
'dataType' : 'json',
|
||||
'success' : _.bind(function(resp) {
|
||||
// NEWSBLUR.log(['resp', resp]);
|
||||
if ((resp.activated_subs >= resp.total_subs || resp.code < 0)) {
|
||||
this.homepage();
|
||||
} else if (resp.activated_subs != resp.total_subs) {
|
||||
this.retries += 1;
|
||||
$('.NB-paypal-return-loading').progressbar({
|
||||
value: (resp.activated_subs / resp.total_subs) * 100
|
||||
});
|
||||
}
|
||||
}, this),
|
||||
'error' : _.bind(function() {
|
||||
this.retries += 1;
|
||||
if (this.retries > 30) {
|
||||
this.homepage();
|
||||
}
|
||||
}, this)
|
||||
});
|
||||
},
|
||||
|
||||
homepage: function() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -31,20 +31,19 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
|
||||
this.$modal = $.make('div', { className: 'NB-modal-feedchooser NB-modal' }, [
|
||||
$.make('h2', { className: 'NB-modal-title' }, 'Choose Your '+this.MAX_FEEDS),
|
||||
$.make('h2', { className: 'NB-modal-subtitle' }, [
|
||||
$.make('b', [
|
||||
'You have a ',
|
||||
$.make('span', { style: 'color: #303060;' }, 'Standard Account'),
|
||||
', which can follow up to '+this.MAX_FEEDS+' sites.'
|
||||
]),
|
||||
'You can always change these.'
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-type'}, [
|
||||
$.make('div', { className: 'NB-feedchooser-info'}, [
|
||||
$.make('div', { className: 'NB-feedchooser-info-type' }, [
|
||||
$.make('span', { className: 'NB-feedchooser-subtitle-type-prefix' }, 'Free'),
|
||||
' Standard Account'
|
||||
]),
|
||||
$.make('h2', { className: 'NB-modal-subtitle' }, [
|
||||
$.make('b', [
|
||||
'You can follow up to '+this.MAX_FEEDS+' sites.'
|
||||
]),
|
||||
$.make('br'),
|
||||
'You can always change these.'
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-info-counts'}),
|
||||
$.make('div', { className: 'NB-feedchooser-info-sort'}, 'Auto-Selected By Popularity')
|
||||
]),
|
||||
|
@ -70,7 +69,7 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
$.make('ul', { className: 'NB-feedchooser-premium-bullets' }, [
|
||||
$.make('li', { className: 'NB-1' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-premium-bullet-image' }),
|
||||
'Sites are updated 2-10x more often'
|
||||
'Sites are updated up to 10x more often.'
|
||||
]),
|
||||
$.make('li', { className: 'NB-2' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-premium-bullet-image' }),
|
||||
|
@ -78,8 +77,7 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
]),
|
||||
$.make('li', { className: 'NB-3' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-premium-bullet-image' }),
|
||||
'Access to the premium-only River of News: ',
|
||||
'stories from multiple sites in a single feed'
|
||||
'River of News (reading by folder).'
|
||||
]),
|
||||
$.make('li', { className: 'NB-4' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-premium-bullet-image' }),
|
||||
|
@ -102,22 +100,48 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
]),
|
||||
$.make('div', { className: 'NB-modal-submit NB-modal-submit-paypal' }, [
|
||||
// this.make_google_checkout()
|
||||
$.make('div', { className: 'NB-feedchooser-paypal' }),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-value NB-1' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-image' }),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-month' }, '$12/year'),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-month' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-image' }),
|
||||
'$12/year'
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-year' }, '($1/month)')
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-value NB-2' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-image' }),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-month' }, '$24/year'),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-month' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-image' }),
|
||||
'$24/year'
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-year' }, '($2/month)')
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-value NB-3' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-image' }),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-month' }, '$48/year'),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-year' }, '($4/month)')
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-month' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-image' }),
|
||||
'$36/year'
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-dollar-year' }, '($3/month)')
|
||||
])
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-processor' }, [
|
||||
$.make('div', { className: 'NB-feedchooser-paypal' }, [
|
||||
$.make('img', { src: NEWSBLUR.Globals.MEDIA_URL + '/img/reader/logo-paypal.png', height: 30 }),
|
||||
$.make('div', { className: 'NB-feedchooser-paypal-form' })
|
||||
]),
|
||||
$.make('div', { className: 'NB-feedchooser-stripe' }, [
|
||||
$.make('div', { className: 'NB-creditcards' }, [
|
||||
$.make('img', { src: "https://manage.stripe.com/img/credit_cards/visa.png" }),
|
||||
$.make('img', { src: "https://manage.stripe.com/img/credit_cards/mastercard.png" }),
|
||||
$.make('img', { src: "https://manage.stripe.com/img/credit_cards/amex.png" }),
|
||||
$.make('img', { src: "https://manage.stripe.com/img/credit_cards/discover.png" })
|
||||
]),
|
||||
$.make('div', {
|
||||
className: "NB-stripe-button NB-modal-submit-button NB-modal-submit-green"
|
||||
}, [
|
||||
"Pay by",
|
||||
$.make('br'),
|
||||
"Credit Card"
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
|
@ -127,7 +151,7 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
|
||||
make_paypal_button: function() {
|
||||
var self = this;
|
||||
var $paypal = $('.NB-feedchooser-paypal', this.$modal);
|
||||
var $paypal = $('.NB-feedchooser-paypal-form', this.$modal);
|
||||
$.get('/profile/paypal_form', function(response) {
|
||||
$paypal.html(response);
|
||||
self.choose_dollar_amount(2);
|
||||
|
@ -375,6 +399,10 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
open_stripe_form: function() {
|
||||
window.location.href = "https://" + NEWSBLUR.URLs.domain + "/profile/stripe_form?plan=" + this.plan;
|
||||
},
|
||||
|
||||
update_homepage_count: function() {
|
||||
var $count = $('.NB-module-account-feedcount');
|
||||
var $button = $('.NB-module-account-upgrade');
|
||||
|
@ -385,18 +413,20 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
$('.NB-module-account-trainer').removeClass('NB-hidden').hide().slideDown(500);
|
||||
},
|
||||
|
||||
choose_dollar_amount: function(step) {
|
||||
choose_dollar_amount: function(plan) {
|
||||
var $value = $('.NB-feedchooser-dollar-value', this.$modal);
|
||||
var $input = $('input[name=a3]');
|
||||
|
||||
this.plan = plan;
|
||||
|
||||
$value.removeClass('NB-selected');
|
||||
$value.filter('.NB-'+step).addClass('NB-selected');
|
||||
if (step == 1) {
|
||||
$value.filter('.NB-'+plan).addClass('NB-selected');
|
||||
if (plan == 1) {
|
||||
$input.val(12);
|
||||
} else if (step == 2) {
|
||||
} else if (plan == 2) {
|
||||
$input.val(24);
|
||||
} else if (step == 3) {
|
||||
$input.val(48);
|
||||
} else if (plan == 3) {
|
||||
$input.val(36);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -428,6 +458,11 @@ NEWSBLUR.ReaderFeedchooser.prototype = {
|
|||
this.close_and_add();
|
||||
}, this));
|
||||
|
||||
$.targetIs(e, { tagSelector: '.NB-stripe-button' }, _.bind(function($t, $p) {
|
||||
e.preventDefault();
|
||||
this.open_stripe_form();
|
||||
}, this));
|
||||
|
||||
$.targetIs(e, { tagSelector: '.NB-feedchooser-dollar-value' }, _.bind(function($t, $p) {
|
||||
e.preventDefault();
|
||||
var step;
|
||||
|
|
153
settings.py
153
settings.py
|
@ -39,10 +39,12 @@ if '/utils' not in ' '.join(sys.path):
|
|||
sys.path.append(UTILS_ROOT)
|
||||
if '/vendor' not in ' '.join(sys.path):
|
||||
sys.path.append(VENDOR_ROOT)
|
||||
|
||||
# ===================
|
||||
# = Global Settings =
|
||||
# ===================
|
||||
|
||||
DEBUG = False
|
||||
TEST_DEBUG = False
|
||||
SEND_BROKEN_LINK_EMAILS = False
|
||||
MANAGERS = ADMINS
|
||||
|
@ -60,7 +62,7 @@ ADMIN_MEDIA_PREFIX = '/media/admin/'
|
|||
SECRET_KEY = 'YOUR_SECRET_KEY'
|
||||
EMAIL_BACKEND = 'django_ses.SESBackend'
|
||||
CIPHER_USERNAMES = False
|
||||
|
||||
DEBUG_ASSETS = DEBUG
|
||||
|
||||
# ===============
|
||||
# = Enviornment =
|
||||
|
@ -160,145 +162,6 @@ LOGGING = {
|
|||
}
|
||||
}
|
||||
|
||||
# =====================
|
||||
# = Media Compression =
|
||||
# =====================
|
||||
|
||||
COMPRESS_JS = {
|
||||
'all': {
|
||||
'source_filenames': (
|
||||
'js/jquery-1.7.1.js',
|
||||
'js/inflector.js',
|
||||
'js/jquery.json.js',
|
||||
'js/jquery.easing.js',
|
||||
'js/jquery.newsblur.js',
|
||||
'js/jquery.scrollTo.js',
|
||||
'js/jquery.corners.js',
|
||||
'js/jquery.hotkeys.js',
|
||||
'js/jquery.ajaxupload.js',
|
||||
'js/jquery.ajaxmanager.3.js',
|
||||
'js/jquery.simplemodal-1.3.js',
|
||||
'js/jquery.color.js',
|
||||
'js/jquery.rightclick.js',
|
||||
'js/jquery.ui.core.js',
|
||||
'js/jquery.ui.widget.js',
|
||||
'js/jquery.ui.mouse.js',
|
||||
'js/jquery.ui.position.js',
|
||||
'js/jquery.ui.draggable.js',
|
||||
'js/jquery.ui.sortable.js',
|
||||
'js/jquery.ui.slider.js',
|
||||
'js/jquery.ui.autocomplete.js',
|
||||
'js/jquery.ui.progressbar.js',
|
||||
'js/jquery.layout.js',
|
||||
'js/jquery.tinysort.js',
|
||||
'js/jquery.fieldselection.js',
|
||||
'js/jquery.flot.js',
|
||||
'js/jquery.tipsy.js',
|
||||
# 'js/socket.io-client.0.8.7.js',
|
||||
'js/underscore.js',
|
||||
'js/underscore.string.js',
|
||||
'js/backbone-0.5.3.js',
|
||||
'js/newsblur/reader_utils.js',
|
||||
'js/newsblur/assetmodel.js',
|
||||
'js/newsblur/reader.js',
|
||||
'js/newsblur/generate_bookmarklet.js',
|
||||
'js/newsblur/modal.js',
|
||||
'js/newsblur/reader_classifier.js',
|
||||
'js/newsblur/reader_add_feed.js',
|
||||
'js/newsblur/reader_mark_read.js',
|
||||
'js/newsblur/reader_goodies.js',
|
||||
'js/newsblur/reader_preferences.js',
|
||||
'js/newsblur/reader_account.js',
|
||||
'js/newsblur/reader_feedchooser.js',
|
||||
'js/newsblur/reader_statistics.js',
|
||||
'js/newsblur/reader_feed_exception.js',
|
||||
'js/newsblur/reader_keyboard.js',
|
||||
'js/newsblur/reader_recommend_feed.js',
|
||||
'js/newsblur/reader_send_email.js',
|
||||
'js/newsblur/reader_tutorial.js',
|
||||
'js/newsblur/reader_friends.js',
|
||||
'js/newsblur/about.js',
|
||||
'js/newsblur/faq.js',
|
||||
),
|
||||
'output_filename': 'js/all-compressed-?.js'
|
||||
},
|
||||
'mobile': {
|
||||
'source_filenames': (
|
||||
'js/jquery-1.7.1.js',
|
||||
'js/mobile/jquery.mobile-1.0b1.js',
|
||||
'js/jquery.ajaxmanager.3.js',
|
||||
'js/underscore.js',
|
||||
'js/underscore.string.js',
|
||||
'js/inflector.js',
|
||||
'js/jquery.json.js',
|
||||
'js/jquery.easing.js',
|
||||
'js/jquery.newsblur.js',
|
||||
'js/newsblur/reader_utils.js',
|
||||
'js/newsblur/assetmodel.js',
|
||||
'js/mobile/newsblur/mobile_workspace.js',
|
||||
),
|
||||
'output_filename': 'js/mobile-compressed-?.js',
|
||||
},
|
||||
'paypal': {
|
||||
'source_filenames': (
|
||||
'js/newsblur/paypal_return.js',
|
||||
),
|
||||
'output_filename': 'js/paypal-compressed-?.js',
|
||||
},
|
||||
'bookmarklet': {
|
||||
'source_filenames': (
|
||||
'js/jquery-1.5.1.min.js',
|
||||
'js/jquery.noConflict.js',
|
||||
'js/jquery.newsblur.js',
|
||||
'js/jquery.tinysort.js',
|
||||
'js/jquery.simplemodal-1.3.js',
|
||||
'js/jquery.corners.js',
|
||||
),
|
||||
'output_filename': 'js/bookmarklet-compressed-?.js',
|
||||
},
|
||||
}
|
||||
|
||||
COMPRESS_CSS = {
|
||||
'all': {
|
||||
'source_filenames': (
|
||||
'css/reader.css',
|
||||
'css/modals.css',
|
||||
'css/status.css',
|
||||
'css/jquery-ui/jquery.theme.css',
|
||||
'css/jquery.tipsy.css',
|
||||
),
|
||||
'output_filename': 'css/all-compressed-?.css'
|
||||
},
|
||||
'mobile': {
|
||||
'source_filenames': (
|
||||
'css/mobile/jquery.mobile-1.0b1.css',
|
||||
'css/mobile/mobile.css',
|
||||
),
|
||||
'output_filename': 'css/mobile/mobile-compressed-?.css',
|
||||
},
|
||||
'paypal': {
|
||||
'source_filenames': (
|
||||
'css/paypal_return.css',
|
||||
),
|
||||
'output_filename': 'css/paypal-compressed-?.css',
|
||||
},
|
||||
'bookmarklet': {
|
||||
'source_filenames': (
|
||||
'css/reset.css',
|
||||
'css/modals.css',
|
||||
),
|
||||
'output_filename': 'css/paypal-compressed-?.css',
|
||||
},
|
||||
}
|
||||
|
||||
COMPRESS_VERSION = True
|
||||
COMPRESS_JS_FILTERS = ['compress.filters.jsmin.JSMinFilter']
|
||||
COMPRESS_CSS_FILTERS = []
|
||||
|
||||
# YUI_DIR = ''.join([UTILS_ROOT, '/yuicompressor-2.4.2/build/yuicompressor-2.4.2.jar'])
|
||||
# COMPRESS_YUI_BINARY = 'java -jar ' + YUI_DIR
|
||||
# COMPRESS_YUI_JS_ARGUMENTS = '--preserve-semi --nomunge --disable-optimizations'
|
||||
|
||||
# ==========================
|
||||
# = Miscellaneous Settings =
|
||||
# ==========================
|
||||
|
@ -338,7 +201,6 @@ INSTALLED_APPS = (
|
|||
'django.contrib.admin',
|
||||
'django_extensions',
|
||||
'djcelery',
|
||||
# 'seacucumber',
|
||||
'django_ses',
|
||||
'apps.rss_feeds',
|
||||
'apps.reader',
|
||||
|
@ -355,12 +217,21 @@ INSTALLED_APPS = (
|
|||
'vendor',
|
||||
'vendor.typogrify',
|
||||
'vendor.paypal.standard.ipn',
|
||||
'vendor.zebra',
|
||||
)
|
||||
|
||||
if not DEVELOPMENT:
|
||||
INSTALLED_APPS += (
|
||||
'gunicorn',
|
||||
)
|
||||
|
||||
# ==========
|
||||
# = Stripe =
|
||||
# ==========
|
||||
|
||||
STRIPE_SECRET = "YOUR-SECRET-API-KEY"
|
||||
STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY"
|
||||
ZEBRA_ENABLE_APP = True
|
||||
|
||||
# ==========
|
||||
# = Celery =
|
||||
|
|
|
@ -67,18 +67,6 @@
|
|||
</script>
|
||||
|
||||
{% include_stylesheets "common" %}
|
||||
{% block head_js %}
|
||||
{% include_javascripts "common" %}
|
||||
{% endblock head_js %}
|
||||
{% block extra_head_js %}
|
||||
{% endblock extra_head_js %}
|
||||
{% block head %}{% endblock %}
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$.extend(NEWSBLUR.Preferences, {% if user_profile.preferences %}{{ user_profile.preferences|safe }}{% else %}{}{% endif %});
|
||||
$.extend(NEWSBLUR.Preferences['view_settings'], {% if user_profile.view_settings %}{{ user_profile.view_settings|safe }}{% else %}{}{% endif %});
|
||||
$.extend(NEWSBLUR.Preferences['collapsed_folders'], {% if user_profile.collapsed_folders %}{{ user_profile.collapsed_folders|safe }}{% else %}[]{% endif %});
|
||||
</script>
|
||||
|
||||
{% if not debug %}
|
||||
<script type="text/javascript">
|
||||
|
@ -127,19 +115,16 @@
|
|||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
{% if not debug %}
|
||||
<!-- Start Quantcast tag -->
|
||||
<script type="text/javascript">
|
||||
_qoptions={
|
||||
qacct:"p-0dE65XaLY51Og"
|
||||
};
|
||||
</script>
|
||||
<script type="text/javascript" src="http://edge.quantserve.com/quant.js"></script>
|
||||
<noscript>
|
||||
<img src="http://pixel.quantserve.com/pixel/p-0dE65XaLY51Og.gif" style="display: none;" border="0" height="1" width="1" alt="Quantcast"/>
|
||||
</noscript>
|
||||
<!-- End Quantcast tag -->
|
||||
{% endif %}
|
||||
|
||||
{% block head_js %}
|
||||
{% include_javascripts "common" %}
|
||||
{% endblock head_js %}
|
||||
{% block extra_head_js %}
|
||||
{% endblock extra_head_js %}
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$.extend(NEWSBLUR.Preferences, {% if user_profile.preferences %}{{ user_profile.preferences|safe }}{% else %}{}{% endif %});
|
||||
$.extend(NEWSBLUR.Preferences['view_settings'], {% if user_profile.view_settings %}{{ user_profile.view_settings|safe }}{% else %}{}{% endif %});
|
||||
$.extend(NEWSBLUR.Preferences['collapsed_folders'], {% if user_profile.collapsed_folders %}{{ user_profile.collapsed_folders|safe }}{% else %}[]{% endif %});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,7 +8,7 @@ Stay up to date and in touch with me, yr. developer, in a few different ways:
|
|||
|
||||
* Follow @samuelclay on Twitter: http://twitter.com/samuelclay/
|
||||
* Follow @newsblur on Twitter: http://twitter.com/newsblur/
|
||||
* Follow the constantly evolving source code on GitHub: http://github.com/samuelclay/
|
||||
* Follow @samuelclay on GitHub: http://github.com/samuelclay/
|
||||
|
||||
{% block resources_header %}There are a few resources you can use if you end up loving NewsBlur:{% endblock resources_header %}
|
||||
|
||||
|
@ -17,7 +17,7 @@ Stay up to date and in touch with me, yr. developer, in a few different ways:
|
|||
|
||||
There's plenty of ways to use NewsBlur beyond the website:
|
||||
|
||||
* Download the iPhone App -- Coming soon, it's in review on the App Store.
|
||||
* Download the free iPhone App: http://www.newsblur.com/iphone/
|
||||
* Download the Android App on the Android Market: https://market.android.com/details?id=bitwrit.Blar
|
||||
* Download browser extensions for Safari, Firefox, and Chrome: http://www.newsblur.com{{ user.profile.autologin_url }}?next=goodies
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<ul style="list-style: none;">
|
||||
<li style="line-height:22px;"><a href="http://twitter.com/samuelclay/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/twitter_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @samuelclay on Twitter</a>.</li>
|
||||
<li style="line-height:22px;"><a href="http://twitter.com/newsblur/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/twitter.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @newsblur on Twitter</a>.</li>
|
||||
<li style="line-height:22px;"><a href="http://github.com/samuelclay/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/github_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow the constantly evolving source code on GitHub</a>.</li>
|
||||
<li style="line-height:22px;"><a href="http://github.com/samuelclay/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/github_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @samuelclay on GitHub</a>. ← I live on props.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p style="line-height: 20px;">{% block resources_header %}There are a couple resources you can use if you end up loving NewsBlur:{% endblock resources_header %}</p>
|
||||
|
@ -46,7 +46,7 @@
|
|||
<p style="line-height: 20px;">There's plenty of ways to use NewsBlur beyond the website:</p>
|
||||
<p style="line-height: 20px;">
|
||||
<ul style="list-style: none;">
|
||||
<li style="line-height:22px;"><img src="http://www.newsblur.com/media/img/reader/iphone_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> <i>iPhone App -- Coming soon, it's in review on the App Store.</i></li>
|
||||
<li style="line-height:22px;"><a href="http://www.newsblur.com/iphone/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/iphone_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Download the free iPhone App</a></li>
|
||||
<li style="line-height:22px;"><a href="https://market.android.com/details?id=bitwrit.Blar" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/android_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Download the Android App on the Android Market</a>.</li>
|
||||
<li style="line-height:22px;"><a href="http://www.newsblur.com{{ user.profile.autologin_url }}?next=goodies" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/icons/silk/package_green.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Download browser extensions for Safari, Firefox, and Chrome</a>.</li>
|
||||
</ul>
|
||||
|
|
73
templates/profile/stripe_form.xhtml
Normal file
73
templates/profile/stripe_form.xhtml
Normal file
|
@ -0,0 +1,73 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load typogrify_tags utils_tags zebra_tags %}
|
||||
|
||||
{% block bodyclass %}NB-static{% endblock %}
|
||||
{% block extra_head_js %}
|
||||
{% include_stylesheets "common" %}
|
||||
{% include_javascripts "payments" %}
|
||||
|
||||
<script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.8.1/jquery.validate.min.js"></script>
|
||||
<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
|
||||
|
||||
{% zebra_head_and_stripe_key %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="NB-stripe-form-wrapper">
|
||||
<div class="NB-stripe-form">
|
||||
<label>Username</label>
|
||||
<div class="NB-stripe-username">{{ user.username }}</div>
|
||||
<div class="NB-creditcards">
|
||||
<img src="https://manage.stripe.com/img/credit_cards/visa.png">
|
||||
<img src="https://manage.stripe.com/img/credit_cards/mastercard.png">
|
||||
<img src="https://manage.stripe.com/img/credit_cards/amex.png">
|
||||
<img src="https://manage.stripe.com/img/credit_cards/discover.png">
|
||||
</div>
|
||||
|
||||
<form action="" method="POST" id="payment-form">{% csrf_token %}
|
||||
|
||||
<div>
|
||||
{{ zebra_form.card_number.label_tag }}
|
||||
{{ zebra_form.card_number }}
|
||||
</div>
|
||||
<div>
|
||||
{{ zebra_form.card_cvv.label_tag }}
|
||||
{{ zebra_form.card_cvv }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ zebra_form.card_expiry_month.label_tag }}
|
||||
{{ zebra_form.card_expiry_month }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ zebra_form.card_expiry_year.label_tag }}
|
||||
{{ zebra_form.card_expiry_year }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ zebra_form.email.label_tag }}
|
||||
{{ zebra_form.email }}
|
||||
</div>
|
||||
|
||||
<div style="overflow: hidden">
|
||||
{{ zebra_form.plan.label_tag }}
|
||||
{{ zebra_form.plan|safe }}
|
||||
</div>
|
||||
|
||||
{{ zebra_form.last_4_digits }}
|
||||
{{ zebra_form.stripe_token }}
|
||||
|
||||
<noscript><h3>Note: this form requires Javascript to use.</h3></noscript>
|
||||
|
||||
<span class="payment-errors"></span>
|
||||
|
||||
<button type="submit" class="submit-button NB-modal-submit-button NB-modal-submit-green">Submit Payment</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -2,15 +2,29 @@
|
|||
|
||||
{% load typogrify_tags recommendations_tags utils_tags statistics_tags %}
|
||||
|
||||
{% block extra_head_js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
NEWSBLUR.reader = new NEWSBLUR.Reader();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% if user.is_staff %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#add-feature-button').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('#add-feature-form').fadeIn(500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
NEWSBLUR.reader = new NEWSBLUR.Reader();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1 class="NB-splash-heading">NewsBlur</h1>
|
||||
<h2 class="NB-splash-heading">- A visual feed reader with intelligence.</h2>
|
||||
|
@ -28,14 +42,6 @@ $(document).ready(function() {
|
|||
<div class="NB-features-add">
|
||||
<a href="#" id="add-feature-button" class="NB-splash-link">Add</a>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#add-feature-button').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('#add-feature-form').fadeIn(500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
<div class="NB-spinner NB-left"></div>
|
||||
<a href="#" class="NB-module-direction NB-module-next-page NB-javascript"></a>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block extra_head_js %}
|
||||
{% include_stylesheets "common" %}
|
||||
{% include_javascripts "paypal" %}
|
||||
{% include_javascripts "payments" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
1
urls.py
1
urls.py
|
@ -28,6 +28,7 @@ urlpatterns = patterns('',
|
|||
url(r'^press/?', static_views.press, name='press'),
|
||||
url(r'^feedback/?', static_views.feedback, name='feedback'),
|
||||
url(r'^iphone/?', static_views.iphone, name='iphone'),
|
||||
url(r'zebra/', include('zebra.urls', namespace="zebra", app_name='zebra')),
|
||||
)
|
||||
|
||||
if settings.DEVELOPMENT:
|
||||
|
|
|
@ -284,6 +284,8 @@ class Dispatcher:
|
|||
if self.options.get('fake'):
|
||||
skip = True
|
||||
weight = "-"
|
||||
quick = "-"
|
||||
rand = "-"
|
||||
elif self.options.get('quick'):
|
||||
weight = feed.stories_last_month * feed.num_subscribers
|
||||
random_weight = random.randint(1, max(weight, 1))
|
||||
|
@ -292,10 +294,11 @@ class Dispatcher:
|
|||
if random_weight < 100 and rand < quick:
|
||||
skip = True
|
||||
if skip:
|
||||
logging.debug(' ---> [%-30s] ~BGFaking fetch, skipping (%s/month, %s subs)...' % (
|
||||
logging.debug(' ---> [%-30s] ~BGFaking fetch, skipping (%s/month, %s subs, %s < %s)...' % (
|
||||
unicode(feed)[:30],
|
||||
weight,
|
||||
feed.num_subscribers))
|
||||
feed.num_subscribers,
|
||||
rand, quick))
|
||||
continue
|
||||
|
||||
ffeed = FetchFeed(feed_id, self.options)
|
||||
|
|
|
@ -36,7 +36,7 @@ class JammitAssets:
|
|||
`use_compressed_assets` profile setting.
|
||||
"""
|
||||
tags = []
|
||||
if not settings.DEBUG:
|
||||
if not getattr(settings, 'DEBUG_ASSETS', settings.DEBUG):
|
||||
if asset_type == 'javascripts':
|
||||
asset_type_ext = 'js'
|
||||
elif asset_type == 'stylesheets':
|
||||
|
|
|
@ -70,7 +70,7 @@ def json_encode(data, *args, **kwargs):
|
|||
# see http://code.djangoproject.com/ticket/5868
|
||||
elif isinstance(data, Promise):
|
||||
ret = force_unicode(data)
|
||||
elif isinstance(data, datetime.datetime):
|
||||
elif isinstance(data, datetime.datetime) or isinstance(data, datetime.date):
|
||||
ret = str(data)
|
||||
else:
|
||||
ret = data
|
||||
|
|
4
vendor/paypal/standard/conf.py
vendored
4
vendor/paypal/standard/conf.py
vendored
|
@ -16,6 +16,6 @@ SANDBOX_POSTBACK_ENDPOINT = "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
|||
|
||||
# Images
|
||||
IMAGE = getattr(settings, "PAYPAL_IMAGE", "http://images.paypal.com/images/x-click-but01.gif")
|
||||
SUBSCRIPTION_IMAGE = "https://www.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif"
|
||||
SUBSCRIPTION_IMAGE = "https://www.paypal.com/en_US/i/btn/btn_subscribe_LG.gif"
|
||||
SANDBOX_IMAGE = getattr(settings, "PAYPAL_SANDBOX_IMAGE", "https://www.sandbox.paypal.com/en_US/i/btn/btn_buynowCC_LG.gif")
|
||||
SUBSCRIPTION_SANDBOX_IMAGE = "https://www.sandbox.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif"
|
||||
SUBSCRIPTION_SANDBOX_IMAGE = "https://www.sandbox.paypal.com/en_US/i/btn/btn_subscribe_LG.gif"
|
0
vendor/zebra/__init__.py
vendored
Executable file
0
vendor/zebra/__init__.py
vendored
Executable file
10
vendor/zebra/admin.py
vendored
Executable file
10
vendor/zebra/admin.py
vendored
Executable file
|
@ -0,0 +1,10 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from zebra.conf import options
|
||||
|
||||
if options.ZEBRA_ENABLE_APP:
|
||||
from zebra.models import Customer, Plan, Subscription
|
||||
|
||||
admin.site.register(Customer)
|
||||
admin.site.register(Plan)
|
||||
admin.site.register(Subscription)
|
0
vendor/zebra/conf/__init__.py
vendored
Executable file
0
vendor/zebra/conf/__init__.py
vendored
Executable file
59
vendor/zebra/conf/options.py
vendored
Executable file
59
vendor/zebra/conf/options.py
vendored
Executable file
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
Default settings for zebra
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from django.conf import settings as _settings
|
||||
|
||||
|
||||
if hasattr(_settings, 'STRIPE_PUBLISHABLE'):
|
||||
STRIPE_PUBLISHABLE = getattr(_settings, 'STRIPE_PUBLISHABLE')
|
||||
else:
|
||||
try:
|
||||
STRIPE_PUBLISHABLE = os.environ['STRIPE_PUBLISHABLE']
|
||||
except KeyError:
|
||||
STRIPE_PUBLISHABLE = ''
|
||||
|
||||
if hasattr(_settings, 'STRIPE_SECRET'):
|
||||
STRIPE_SECRET = getattr(_settings, 'STRIPE_SECRET')
|
||||
else:
|
||||
try:
|
||||
STRIPE_SECRET = os.environ['STRIPE_SECRET']
|
||||
except KeyError:
|
||||
STRIPE_SECRET = ''
|
||||
|
||||
ZEBRA_ENABLE_APP = getattr(_settings, 'ZEBRA_ENABLE_APP', False)
|
||||
ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS = getattr(_settings,
|
||||
'ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS', True)
|
||||
|
||||
_today = datetime.date.today()
|
||||
ZEBRA_CARD_YEARS = getattr(_settings, 'ZEBRA_CARD_YEARS',
|
||||
range(_today.year, _today.year+12))
|
||||
ZEBRA_CARD_YEARS_CHOICES = getattr(_settings, 'ZEBRA_CARD_YEARS_CHOICES',
|
||||
[(i,i) for i in ZEBRA_CARD_YEARS])
|
||||
|
||||
ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE = getattr(_settings,
|
||||
'ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE', 100)
|
||||
|
||||
_audit_defaults = {
|
||||
'active': 'active',
|
||||
'no_subscription': 'no_subscription',
|
||||
'past_due': 'past_due',
|
||||
'suspended': 'suspended',
|
||||
'trialing': 'trialing',
|
||||
'unpaid': 'unpaid',
|
||||
'cancelled': 'cancelled'
|
||||
}
|
||||
|
||||
ZEBRA_AUDIT_RESULTS = getattr(_settings, 'ZEBRA_AUDIT_RESULTS', _audit_defaults)
|
||||
|
||||
ZEBRA_ACTIVE_STATUSES = getattr(_settings, 'ZEBRA_ACTIVE_STATUSES',
|
||||
('active', 'past_due', 'trialing'))
|
||||
ZEBRA_INACTIVE_STATUSES = getattr(_settings, 'ZEBRA_INACTIVE_STATUSES',
|
||||
('cancelled', 'suspended', 'unpaid', 'no_subscription'))
|
||||
|
||||
if ZEBRA_ENABLE_APP:
|
||||
ZEBRA_CUSTOMER_MODEL = getattr(_settings, 'ZEBRA_CUSTOMER_MODEL', 'zebra.Customer')
|
||||
else:
|
||||
ZEBRA_CUSTOMER_MODEL = getattr(_settings, 'ZEBRA_CUSTOMER_MODEL', None)
|
36
vendor/zebra/forms.py
vendored
Executable file
36
vendor/zebra/forms.py
vendored
Executable file
|
@ -0,0 +1,36 @@
|
|||
from django import forms
|
||||
from django.core.exceptions import NON_FIELD_ERRORS
|
||||
from django.utils.dates import MONTHS
|
||||
|
||||
from zebra.conf import options
|
||||
from zebra.widgets import NoNameSelect, NoNameTextInput
|
||||
|
||||
|
||||
class MonospaceForm(forms.Form):
|
||||
def addError(self, message):
|
||||
self._errors[NON_FIELD_ERRORS] = self.error_class([message])
|
||||
|
||||
|
||||
class CardForm(MonospaceForm):
|
||||
last_4_digits = forms.CharField(required=True, min_length=4, max_length=4,
|
||||
widget=forms.HiddenInput())
|
||||
stripe_token = forms.CharField(required=True, widget=forms.HiddenInput())
|
||||
|
||||
|
||||
class StripePaymentForm(CardForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StripePaymentForm, self).__init__(*args, **kwargs)
|
||||
self.fields['card_cvv'].label = "Card CVC"
|
||||
self.fields['card_cvv'].help_text = "Card Verification Code; see rear of card."
|
||||
months = [ (m[0], u'%02d - %s' % (m[0], unicode(m[1])))
|
||||
for m in sorted(MONTHS.iteritems()) ]
|
||||
self.fields['card_expiry_month'].choices = months
|
||||
|
||||
card_number = forms.CharField(required=False, max_length=20,
|
||||
widget=NoNameTextInput())
|
||||
card_cvv = forms.CharField(required=False, max_length=4,
|
||||
widget=NoNameTextInput())
|
||||
card_expiry_month = forms.ChoiceField(required=False, widget=NoNameSelect(),
|
||||
choices=MONTHS.iteritems())
|
||||
card_expiry_year = forms.ChoiceField(required=False, widget=NoNameSelect(),
|
||||
choices=options.ZEBRA_CARD_YEARS_CHOICES)
|
0
vendor/zebra/management/__init__.py
vendored
Executable file
0
vendor/zebra/management/__init__.py
vendored
Executable file
0
vendor/zebra/management/commands/__init__.py
vendored
Executable file
0
vendor/zebra/management/commands/__init__.py
vendored
Executable file
42
vendor/zebra/management/commands/clear_stripe_test_customers.py
vendored
Executable file
42
vendor/zebra/management/commands/clear_stripe_test_customers.py
vendored
Executable file
|
@ -0,0 +1,42 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
import stripe
|
||||
|
||||
from zebra.conf import options as zoptions
|
||||
|
||||
|
||||
CLEAR_CHUNK_SIZE = zoptions.ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Clear all test mode customers from your stripe account."
|
||||
__test__ = False
|
||||
|
||||
def handle(self, *args, **options):
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
stripe.api_key = zoptions.STRIPE_SECRET
|
||||
customer_chunk = [0]
|
||||
|
||||
if verbosity > 0:
|
||||
print "Clearing stripe test customers:"
|
||||
|
||||
num_checked = 0
|
||||
while len(customer_chunk) is not 0:
|
||||
customer_chunk = stripe.Customer.all(count=CLEAR_CHUNK_SIZE, offset=num_checked).data
|
||||
|
||||
if verbosity > 1:
|
||||
print "Processing records %s-%s" % (num_checked, num_checked+len(customer_chunk))
|
||||
|
||||
for c in customer_chunk:
|
||||
if verbosity > 2:
|
||||
print "Deleting %s..." % (c.description),
|
||||
|
||||
if not c.livemode:
|
||||
c.delete()
|
||||
|
||||
if verbosity > 2:
|
||||
print "done"
|
||||
|
||||
num_checked = num_checked + len(customer_chunk)
|
||||
|
||||
if verbosity > 0:
|
||||
print "Finished clearing stripe test customers."
|
169
vendor/zebra/mixins.py
vendored
Executable file
169
vendor/zebra/mixins.py
vendored
Executable file
|
@ -0,0 +1,169 @@
|
|||
import stripe
|
||||
|
||||
from zebra.conf import options
|
||||
|
||||
|
||||
def _get_attr_value(instance, attr, default=None):
|
||||
"""
|
||||
Simple helper to get the value of an instance's attribute if it exists.
|
||||
|
||||
If the instance attribute is callable it will be called and the result will
|
||||
be returned.
|
||||
|
||||
Optionally accepts a default value to return if the attribute is missing.
|
||||
Defaults to `None`
|
||||
|
||||
>>> class Foo(object):
|
||||
... bar = 'baz'
|
||||
... def hi(self):
|
||||
... return 'hi'
|
||||
>>> f = Foo()
|
||||
>>> _get_attr_value(f, 'bar')
|
||||
'baz'
|
||||
>>> _get_attr_value(f, 'xyz')
|
||||
|
||||
>>> _get_attr_value(f, 'xyz', False)
|
||||
False
|
||||
>>> _get_attr_value(f, 'hi')
|
||||
'hi'
|
||||
"""
|
||||
value = default
|
||||
if hasattr(instance, attr):
|
||||
value = getattr(instance, attr)
|
||||
if callable(value):
|
||||
value = value()
|
||||
return value
|
||||
|
||||
|
||||
class StripeMixin(object):
|
||||
"""
|
||||
Provides a property `stripe` that returns an instance of the Stripe module.
|
||||
|
||||
It optionally supports the ability to set `stripe.api_key` if your class
|
||||
has a `stripe_api_key` attribute (method or property), or if
|
||||
settings has a `STRIPE_SECRET` attribute (method or property).
|
||||
"""
|
||||
def _get_stripe(self):
|
||||
if hasattr(self, 'stripe_api_key'):
|
||||
stripe.api_key = _get_attr_value(self, 'stripe_api_key')
|
||||
elif hasattr(options, 'STRIPE_SECRET'):
|
||||
stripe.api_key = _get_attr_value(options, 'STRIPE_SECRET')
|
||||
return stripe
|
||||
stripe = property(_get_stripe)
|
||||
|
||||
|
||||
class StripeCustomerMixin(object):
|
||||
"""
|
||||
Provides a property property `stripe_customer` that returns a stripe
|
||||
customer instance.
|
||||
|
||||
Your class must provide:
|
||||
|
||||
- an attribute `stripe_customer_id` (method or property)
|
||||
to provide the customer id for the returned instance, and
|
||||
- an attribute `stripe` (method or property) that returns an instance
|
||||
of the Stripe module. StripeMixin is an easy way to get this.
|
||||
|
||||
"""
|
||||
def _get_stripe_customer(self):
|
||||
c = None
|
||||
if _get_attr_value(self, 'stripe_customer_id'):
|
||||
c = self.stripe.Customer.retrieve(_get_attr_value(self,
|
||||
'stripe_customer_id'))
|
||||
if not c and options.ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS:
|
||||
c = self.stripe.Customer.create()
|
||||
self.stripe_customer_id = c.id
|
||||
self.save()
|
||||
|
||||
return c
|
||||
stripe_customer = property(_get_stripe_customer)
|
||||
|
||||
|
||||
class StripeSubscriptionMixin(object):
|
||||
"""
|
||||
Provides a property `stripe` that returns an instance of the Stripe module &
|
||||
additionally adds a property `stripe_subscription` that returns a stripe
|
||||
subscription instance.
|
||||
|
||||
Your class must have an attribute `stripe_customer` (method or property)
|
||||
to provide a customer instance with which to lookup the subscription.
|
||||
"""
|
||||
def _get_stripe_subscription(self):
|
||||
subscription = None
|
||||
customer = _get_attr_value(self, 'stripe_customer')
|
||||
if hasattr(customer, 'subscription'):
|
||||
subscription = customer.subscription
|
||||
return subscription
|
||||
stripe_subscription = property(_get_stripe_subscription)
|
||||
|
||||
|
||||
class StripePlanMixin(object):
|
||||
"""
|
||||
Provides a property `stripe` that returns an instance of the Stripe module &
|
||||
additionally adds a property `stripe_plan` that returns a stripe plan
|
||||
instance.
|
||||
|
||||
Your class must have an attribute `stripe_plan_id` (method or property)
|
||||
to provide the plan id for the returned instance.
|
||||
"""
|
||||
def _get_stripe_plan(self):
|
||||
return stripe.Plan.retrieve(_get_attr_value(self, 'stripe_plan_id'))
|
||||
stripe_plan = property(_get_stripe_plan)
|
||||
|
||||
|
||||
class StripeInvoiceMixin(object):
|
||||
"""
|
||||
Provides a property `stripe` that returns an instance of the Stripe module &
|
||||
additionally adds a property `stripe_invoice` that returns a stripe invoice
|
||||
instance.
|
||||
|
||||
Your class must have an attribute `stripe_invoice_id` (method or property)
|
||||
to provide the invoice id for the returned instance.
|
||||
"""
|
||||
def _get_stripe_invoice(self):
|
||||
return stripe.Invoice.retrieve(_get_attr_value(self,
|
||||
'stripe_invoice_id'))
|
||||
stripe_invoice = property(_get_stripe_invoice)
|
||||
|
||||
|
||||
class StripeInvoiceItemMixin(object):
|
||||
"""
|
||||
Provides a property `stripe` that returns an instance of the Stripe module &
|
||||
additionally adds a property `stripe_invoice_item` that returns a stripe
|
||||
invoice item instance.
|
||||
|
||||
Your class must have an attribute `stripe_invoice_item_id` (method or
|
||||
property) to provide the invoice id for the returned instance.
|
||||
"""
|
||||
def _get_stripe_invoice(self):
|
||||
return stripe.Invoice.retrieve(_get_attr_value(self,
|
||||
'stripe_invoice_item_id'))
|
||||
stripe_invoice = property(_get_stripe_invoice)
|
||||
|
||||
|
||||
class StripeChargeMixin(object):
|
||||
"""
|
||||
Provides a property `stripe` that returns an instance of the Stripe module &
|
||||
additionally adds a property `stripe_invoice_item` that returns a stripe
|
||||
invoice item instance.
|
||||
|
||||
Your class must have an attribute `stripe_invoice_item_id` (method or
|
||||
property) to provide the invoice id for the returned instance.
|
||||
"""
|
||||
def _get_stripe_charge(self):
|
||||
return stripe.Charge.retrieve(_get_attr_value(self, 'stripe_charge_id'))
|
||||
stripe_charge = property(_get_stripe_charge)
|
||||
|
||||
|
||||
class ZebraMixin(StripeMixin, StripeCustomerMixin, StripeSubscriptionMixin,
|
||||
StripePlanMixin, StripeInvoiceMixin, StripeInvoiceItemMixin,
|
||||
StripeChargeMixin):
|
||||
"""
|
||||
Provides all available Stripe mixins in one class.
|
||||
|
||||
`self.stripe`
|
||||
`self.stripe_customer`
|
||||
`self.stripe_subscription`
|
||||
`self.stripe_plan`
|
||||
"""
|
||||
pass
|
60
vendor/zebra/models.py
vendored
Executable file
60
vendor/zebra/models.py
vendored
Executable file
|
@ -0,0 +1,60 @@
|
|||
from django.db import models
|
||||
|
||||
from zebra import mixins
|
||||
from zebra.conf import options
|
||||
|
||||
|
||||
class StripeCustomer(models.Model, mixins.StripeMixin, mixins.StripeCustomerMixin):
|
||||
stripe_customer_id = models.CharField(max_length=50, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s" % self.stripe_customer_id
|
||||
|
||||
|
||||
class StripePlan(models.Model, mixins.StripeMixin, mixins.StripePlanMixin):
|
||||
stripe_plan_id = models.CharField(max_length=50, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s" % self.stripe_plan_id
|
||||
|
||||
|
||||
class StripeSubscription(models.Model, mixins.StripeMixin, mixins.StripeSubscriptionMixin):
|
||||
"""
|
||||
You need to provide a stripe_customer attribute. See zebra.models for an
|
||||
example implimentation.
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
# Non-abstract classes must be enabled in your project's settings.py
|
||||
if options.ZEBRA_ENABLE_APP:
|
||||
class DatesModelBase(models.Model):
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class Customer(DatesModelBase, StripeCustomer):
|
||||
pass
|
||||
|
||||
class Plan(DatesModelBase, StripePlan):
|
||||
pass
|
||||
|
||||
class Subscription(DatesModelBase, StripeSubscription):
|
||||
customer = models.ForeignKey(Customer)
|
||||
plan = models.ForeignKey(Plan)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s: %s" % (self.customer, self.plan)
|
||||
|
||||
@property
|
||||
def stripe_customer(self):
|
||||
return self.customer.stripe_customer
|
122
vendor/zebra/signals.py
vendored
Executable file
122
vendor/zebra/signals.py
vendored
Executable file
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
Provides the following signals:
|
||||
|
||||
V1
|
||||
|
||||
- zebra_webhook_recurring_payment_failed
|
||||
- zebra_webhook_invoice_ready
|
||||
- zebra_webhook_recurring_payment_succeeded
|
||||
- zebra_webhook_subscription_trial_ending
|
||||
- zebra_webhook_subscription_final_payment_attempt_failed
|
||||
- zebra_webhook_subscription_ping_sent
|
||||
|
||||
v2
|
||||
|
||||
- zebra_webhook_charge_succeeded
|
||||
- zebra_webhook_charge_failed
|
||||
- zebra_webhook_charge_refunded
|
||||
- zebra_webhook_charge_disputed
|
||||
- zebra_webhook_customer_created
|
||||
- zebra_webhook_customer_updated
|
||||
- zebra_webhook_customer_deleted
|
||||
- zebra_webhook_customer_subscription_created
|
||||
- zebra_webhook_customer_subscription_updated
|
||||
- zebra_webhook_customer_subscription_deleted
|
||||
- zebra_webhook_customer_subscription_trial_will_end
|
||||
- zebra_webhook_customer_discount_created
|
||||
- zebra_webhook_customer_discount_updated
|
||||
- zebra_webhook_customer_discount_deleted
|
||||
- zebra_webhook_invoice_created
|
||||
- zebra_webhook_invoice_updated
|
||||
- zebra_webhook_invoice_payment_succeeded
|
||||
- zebra_webhook_invoice_payment_failed
|
||||
- zebra_webhook_invoiceitem_created
|
||||
- zebra_webhook_invoiceitem_updated
|
||||
- zebra_webhook_invoiceitem_deleted
|
||||
- zebra_webhook_plan_created
|
||||
- zebra_webhook_plan_updated
|
||||
- zebra_webhook_plan_deleted
|
||||
- zebra_webhook_coupon_created
|
||||
- zebra_webhook_coupon_updated
|
||||
- zebra_webhook_coupon_deleted
|
||||
- zebra_webhook_transfer_created
|
||||
- zebra_webhook_transfer_failed
|
||||
- zebra_webhook_ping
|
||||
"""
|
||||
import django.dispatch
|
||||
|
||||
WEBHOOK_ARGS = ["customer", "full_json"]
|
||||
|
||||
zebra_webhook_recurring_payment_failed = django.dispatch.Signal(providing_args=WEBHOOK_ARGS)
|
||||
zebra_webhook_invoice_ready = django.dispatch.Signal(providing_args=WEBHOOK_ARGS)
|
||||
zebra_webhook_recurring_payment_succeeded = django.dispatch.Signal(providing_args=WEBHOOK_ARGS)
|
||||
zebra_webhook_subscription_trial_ending = django.dispatch.Signal(providing_args=WEBHOOK_ARGS)
|
||||
zebra_webhook_subscription_final_payment_attempt_failed = django.dispatch.Signal(providing_args=WEBHOOK_ARGS)
|
||||
zebra_webhook_subscription_ping_sent = django.dispatch.Signal(providing_args=[])
|
||||
|
||||
# v2 webhooks
|
||||
WEBHOOK2_ARGS = ["full_json"]
|
||||
|
||||
zebra_webhook_charge_succeeded = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_charge_failed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_charge_refunded = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_charge_disputed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_subscription_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_subscription_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_subscription_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_subscription_trial_will_end = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_discount_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_discount_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_customer_discount_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoice_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoice_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoice_payment_succeeded = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoice_payment_failed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoiceitem_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoiceitem_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_invoiceitem_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_plan_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_plan_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_plan_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_coupon_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_coupon_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_coupon_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_transfer_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_transfer_failed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
zebra_webhook_ping = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS)
|
||||
|
||||
WEBHOOK_MAP = {
|
||||
'charge_succeeded': zebra_webhook_charge_succeeded,
|
||||
'charge_failed': zebra_webhook_charge_failed,
|
||||
'charge_refunded': zebra_webhook_charge_refunded,
|
||||
'charge_disputed': zebra_webhook_charge_disputed,
|
||||
'customer_created': zebra_webhook_customer_created,
|
||||
'customer_updated': zebra_webhook_customer_updated,
|
||||
'customer_deleted': zebra_webhook_customer_deleted,
|
||||
'customer_subscription_created': zebra_webhook_customer_subscription_created,
|
||||
'customer_subscription_updated': zebra_webhook_customer_subscription_updated,
|
||||
'customer_subscription_deleted': zebra_webhook_customer_subscription_deleted,
|
||||
'customer_subscription_trial_will_end': zebra_webhook_customer_subscription_trial_will_end,
|
||||
'customer_discount_created': zebra_webhook_customer_discount_created,
|
||||
'customer_discount_updated': zebra_webhook_customer_discount_updated,
|
||||
'customer_discount_deleted': zebra_webhook_customer_discount_deleted,
|
||||
'invoice_created': zebra_webhook_invoice_created,
|
||||
'invoice_updated': zebra_webhook_invoice_updated,
|
||||
'invoice_payment_succeeded': zebra_webhook_invoice_payment_succeeded,
|
||||
'invoice_payment_failed': zebra_webhook_invoice_payment_failed,
|
||||
'invoiceitem_created': zebra_webhook_invoiceitem_created,
|
||||
'invoiceitem_updated': zebra_webhook_invoiceitem_updated,
|
||||
'invoiceitem_deleted': zebra_webhook_invoiceitem_deleted,
|
||||
'plan_created': zebra_webhook_plan_created,
|
||||
'plan_updated': zebra_webhook_plan_updated,
|
||||
'plan_deleted': zebra_webhook_plan_deleted,
|
||||
'coupon_created': zebra_webhook_coupon_created,
|
||||
'coupon_updated': zebra_webhook_coupon_updated,
|
||||
'coupon_deleted': zebra_webhook_coupon_deleted,
|
||||
'transfer_created': zebra_webhook_transfer_created,
|
||||
'transfer_failed': zebra_webhook_transfer_failed,
|
||||
'ping': zebra_webhook_ping,
|
||||
}
|
9
vendor/zebra/static/zebra/card-form.css
vendored
Executable file
9
vendor/zebra/static/zebra/card-form.css
vendored
Executable file
|
@ -0,0 +1,9 @@
|
|||
#id_card_number {
|
||||
width: 15ex;
|
||||
}
|
||||
#id_card_cvv {
|
||||
width: 3ex;
|
||||
}
|
||||
#id_card_expiry_year {
|
||||
width: 10ex;
|
||||
}
|
33
vendor/zebra/static/zebra/card-form.js
vendored
Executable file
33
vendor/zebra/static/zebra/card-form.js
vendored
Executable file
|
@ -0,0 +1,33 @@
|
|||
$(function() {
|
||||
$("#id_card_number").parents("form").submit(function() {
|
||||
if ( $("#id_card_number").is(":visible")) {
|
||||
var form = this;
|
||||
var card = {
|
||||
number: $("#id_card_number").val(),
|
||||
expMonth: $("#id_card_expiry_month").val(),
|
||||
expYear: $("#id_card_expiry_year").val(),
|
||||
cvc: $("#id_card_cvv").val()
|
||||
};
|
||||
|
||||
Stripe.createToken(card, function(status, response) {
|
||||
if (status === 200) {
|
||||
// console.log(status, response);
|
||||
$("#credit-card-errors").hide();
|
||||
$("#id_last_4_digits").val(response.card.last4);
|
||||
$("#id_stripe_token").val(response.id);
|
||||
form.submit();
|
||||
$("button[type=submit]").attr("disabled","disabled").html("Submitting..")
|
||||
} else {
|
||||
$(".payment-errors").text(response.error.message);
|
||||
$("#user_submit").attr("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
});
|
||||
});
|
6
vendor/zebra/templates/zebra/_basic_card_form.html
vendored
Executable file
6
vendor/zebra/templates/zebra/_basic_card_form.html
vendored
Executable file
|
@ -0,0 +1,6 @@
|
|||
<form action="" method="POST" id="payment-form">{% csrf_token %}
|
||||
<noscript><h3>Note: this form requires Javascript to use.</h3></noscript>
|
||||
<span class="payment-errors"></span>
|
||||
{{ zebra_form.as_p }}
|
||||
<button type="submit" class="submit-button NB-modal-submit-button NB-modal-submit-green">Submit Payment</button>
|
||||
</form>
|
3
vendor/zebra/templates/zebra/_stripe_js_and_set_stripe_key.html
vendored
Executable file
3
vendor/zebra/templates/zebra/_stripe_js_and_set_stripe_key.html
vendored
Executable file
|
@ -0,0 +1,3 @@
|
|||
<script type="text/javascript">
|
||||
Stripe.setPublishableKey('{{STRIPE_PUBLISHABLE}}');
|
||||
</script>
|
0
vendor/zebra/templatetags/__init__.py
vendored
Executable file
0
vendor/zebra/templatetags/__init__.py
vendored
Executable file
30
vendor/zebra/templatetags/zebra_tags.py
vendored
Executable file
30
vendor/zebra/templatetags/zebra_tags.py
vendored
Executable file
|
@ -0,0 +1,30 @@
|
|||
from django.core.urlresolvers import reverse
|
||||
from django import template
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from zebra.conf import options
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def _set_up_zebra_form(context):
|
||||
if not "zebra_form" in context:
|
||||
if "form" in context:
|
||||
context["zebra_form"] = context["form"]
|
||||
else:
|
||||
raise Exception, "Missing stripe form."
|
||||
context["STRIPE_PUBLISHABLE"] = options.STRIPE_PUBLISHABLE
|
||||
return context
|
||||
|
||||
|
||||
@register.inclusion_tag('zebra/_stripe_js_and_set_stripe_key.html', takes_context=True)
|
||||
def zebra_head_and_stripe_key(context):
|
||||
return _set_up_zebra_form(context)
|
||||
|
||||
|
||||
@register.inclusion_tag('zebra/_basic_card_form.html', takes_context=True)
|
||||
def zebra_card_form(context):
|
||||
return _set_up_zebra_form(context)
|
8
vendor/zebra/urls.py
vendored
Executable file
8
vendor/zebra/urls.py
vendored
Executable file
|
@ -0,0 +1,8 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
from zebra import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'webhooks/$', views.webhooks, name='webhooks'),
|
||||
url(r'webhooks/v2/$', views.webhooks_v2, name='webhooks_v2'),
|
||||
)
|
26
vendor/zebra/utils.py
vendored
Executable file
26
vendor/zebra/utils.py
vendored
Executable file
|
@ -0,0 +1,26 @@
|
|||
from zebra.conf import options
|
||||
|
||||
AUDIT_RESULTS = options.ZEBRA_AUDIT_RESULTS
|
||||
|
||||
|
||||
def audit_customer_subscription(customer, unknown=True):
|
||||
"""
|
||||
Audits the provided customer's subscription against stripe and returns a pair
|
||||
that contains a boolean and a result type.
|
||||
|
||||
Default result types can be found in zebra.conf.defaults and can be
|
||||
overridden in your project's settings.
|
||||
"""
|
||||
if (hasattr(customer, 'suspended') and customer.suspended):
|
||||
result = AUDIT_RESULTS['suspended']
|
||||
else:
|
||||
if hasattr(customer, 'subscription'):
|
||||
try:
|
||||
result = AUDIT_RESULTS[customer.subscription.status]
|
||||
except KeyError, err:
|
||||
# TODO should this be a more specific exception class?
|
||||
raise Exception("Unable to locate a result set for \
|
||||
subscription status %s in ZEBRA_AUDIT_RESULTS") % str(err)
|
||||
else:
|
||||
result = AUDIT_RESULTS['no_subscription']
|
||||
return result
|
73
vendor/zebra/views.py
vendored
Executable file
73
vendor/zebra/views.py
vendored
Executable file
|
@ -0,0 +1,73 @@
|
|||
from django.http import HttpResponse
|
||||
from django.utils import simplejson
|
||||
from django.db.models import get_model
|
||||
import stripe
|
||||
from zebra.conf import options
|
||||
from zebra.signals import *
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import logging
|
||||
log = logging.getLogger("zebra.%s" % __name__)
|
||||
|
||||
stripe.api_key = options.STRIPE_SECRET
|
||||
|
||||
def _try_to_get_customer_from_customer_id(stripe_customer_id):
|
||||
if options.ZEBRA_CUSTOMER_MODEL:
|
||||
m = get_model(*options.ZEBRA_CUSTOMER_MODEL.split('.'))
|
||||
try:
|
||||
return m.objects.get(stripe_customer_id=stripe_customer_id)
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
@csrf_exempt
|
||||
def webhooks(request):
|
||||
"""
|
||||
Handles all known webhooks from stripe, and calls signals.
|
||||
Plug in as you need.
|
||||
"""
|
||||
|
||||
if request.method != "POST":
|
||||
return HttpResponse("Invalid Request.", status=400)
|
||||
|
||||
json = simplejson.loads(request.POST["json"])
|
||||
|
||||
if json["event"] == "recurring_payment_failed":
|
||||
zebra_webhook_recurring_payment_failed.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json)
|
||||
|
||||
elif json["event"] == "invoice_ready":
|
||||
zebra_webhook_invoice_ready.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json)
|
||||
|
||||
elif json["event"] == "recurring_payment_succeeded":
|
||||
zebra_webhook_recurring_payment_succeeded.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json)
|
||||
|
||||
elif json["event"] == "subscription_trial_ending":
|
||||
zebra_webhook_subscription_trial_ending.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json)
|
||||
|
||||
elif json["event"] == "subscription_final_payment_attempt_failed":
|
||||
zebra_webhook_subscription_final_payment_attempt_failed.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json)
|
||||
|
||||
elif json["event"] == "ping":
|
||||
zebra_webhook_subscription_ping_sent.send(sender=None)
|
||||
|
||||
else:
|
||||
return HttpResponse(status=400)
|
||||
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@csrf_exempt
|
||||
def webhooks_v2(request):
|
||||
"""
|
||||
Handles all known webhooks from stripe, and calls signals.
|
||||
Plug in as you need.
|
||||
"""
|
||||
if request.method != "POST":
|
||||
return HttpResponse("Invalid Request.", status=400)
|
||||
|
||||
event_json = simplejson.loads(request.raw_post_data)
|
||||
event_key = event_json['type'].replace('.', '_')
|
||||
|
||||
if event_key in WEBHOOK_MAP:
|
||||
WEBHOOK_MAP[event_key].send(sender=None, full_json=event_json)
|
||||
|
||||
return HttpResponse(status=200)
|
41
vendor/zebra/widgets.py
vendored
Executable file
41
vendor/zebra/widgets.py
vendored
Executable file
|
@ -0,0 +1,41 @@
|
|||
from django.forms.widgets import Select, TextInput
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
class NoNameWidget(object):
|
||||
|
||||
def _update_to_noname_class_name(self, name, kwargs_dict):
|
||||
if "attrs" in kwargs_dict:
|
||||
if "class" in kwargs_dict["attrs"]:
|
||||
kwargs_dict["attrs"]["class"] += " %s" % (name.replace("_", "-"), )
|
||||
else:
|
||||
kwargs_dict["attrs"].update({'class': name.replace("_", "-")})
|
||||
else:
|
||||
kwargs_dict["attrs"] = {'class': name.replace("_", "-")}
|
||||
|
||||
return kwargs_dict
|
||||
|
||||
def _strip_name_attr(self, widget_string, name):
|
||||
return widget_string.replace("name=\"%s\"" % (name,), "")
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('zebra/card-form.css',)
|
||||
}
|
||||
js = ('zebra/card-form.js', 'https://js.stripe.com/v1/')
|
||||
|
||||
|
||||
|
||||
class NoNameTextInput(TextInput, NoNameWidget):
|
||||
|
||||
def render(self, name, *args, **kwargs):
|
||||
print name, kwargs
|
||||
kwargs = self._update_to_noname_class_name(name, kwargs)
|
||||
return mark_safe(self._strip_name_attr(super(NoNameTextInput, self).render(name, *args, **kwargs), name))
|
||||
|
||||
|
||||
class NoNameSelect(Select, NoNameWidget):
|
||||
|
||||
def render(self, name, *args, **kwargs):
|
||||
kwargs = self._update_to_noname_class_name(name, kwargs)
|
||||
return mark_safe(self._strip_name_attr(super(NoNameSelect, self).render(name, *args, **kwargs), name))
|
Loading…
Add table
Reference in a new issue