From 6b1a1b17025a193cc17fbf079889402ae4ff0b81 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 14 Dec 2018 16:53:34 +0100
Subject: [PATCH 01/16] test payments working (sort of)
---
rowers/forms.py | 6 +-
rowers/templates/payments.html | 295 ++++++++++++++++++++++++++++
rowers/templates/promembership.html | 1 +
rowers/urls.py | 2 +
rowers/views.py | 85 +++++++-
rowsandall_app/settings.py | 17 ++
6 files changed, 404 insertions(+), 2 deletions(-)
create mode 100644 rowers/templates/payments.html
diff --git a/rowers/forms.py b/rowers/forms.py
index 05fc77a8..0994a61e 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -18,7 +18,11 @@ from django.forms import formset_factory
from utils import landingpages
from metrics import axes
-
+# Braintree form
+class BrainTreeForm(forms.Form):
+ amount = forms.FloatField(required=True)
+ payment_method_nonce = forms.CharField(max_length=255)
+
# login form
class LoginForm(forms.Form):
diff --git a/rowers/templates/payments.html b/rowers/templates/payments.html
new file mode 100644
index 00000000..4cc0b8f1
--- /dev/null
+++ b/rowers/templates/payments.html
@@ -0,0 +1,295 @@
+
+{% extends "newbase.html" %}
+{% block title %}Rowsandall Pro Membership{% endblock title %}
+{% block main %}
+{% load rowerfilters %}
+
+
Payments
+
+
+
+
Donations are welcome to keep this web site going. To help cover the hosting
+ costs, I have created several paid plans offering advanced functionality.
+ Once I process your
+ donation, I will give you access to some special features on this
+ website.
+
+
The following table gives an overview of the different plans. As we are
+ constantly developing new functionality, the table might be slightly outdated. Don't
+ hesitate to contact us.
+
+
The Pro membership is open for a free 14 day trial
+
+
+
+
+
+
BASIC
+
PRO
+
SELF-COACH
+
COACH
+
+
+
+
+
Basic rowing metrics (spm, time, distance, heart rate, power)
+
✔
+
✔
+
✔
+
✔
+
+
+
Manual Import, Export, Synchronization and download of all your data
+
✔
+
✔
+
✔
+
✔
+
+
+
Automatic Synchronization with other fitness sites
Create Training plans, tests and challenges for yourself. Track your performance
+ against plan.
+
+
+
✔
+
✔
+
+
+
Create Training plans, tests and challenges for your athletes. Track their performance
+ against plan.
+
+
+
+
✔
+
+
+
Create and manage teams.
+
+
+
+
✔
+
+
+
Manage your athlete's workouts
+
+
+
+
✔
+
+
+
+
+
Coach and Self-Coach Membership
+
+
The Coach plan functionality listed is available to the coach only. Individual athletes
+ can purchase upgrades to "Pro" and "Self-Coach" plans.
+
+
+
Rowsandall.com's Training Planning functionality
+ is part of the paid "Self-Coach" and "Coach" plans.
+
+
On the "Self-Coach" plan, you can plan your own sessions.
+
+
On the "Coach" plan, you can establish teams, see workouts done by
+ athletes on your team, and plan individual and group sessions for your
+ athletes.
+
+
+
If you would like to find a coach who helps you plan your training
+ through rowsandall.com, contact me throught the contact form.
+
+ {% if user.rower.rowerplan == 'basic' and user.rower.protrialexpires|date_dif == 1 %}
+
Free Trial
+
+ You qualify for a 14 day free trial. No credit card needed.
+ Try out Pro or Self-Coach membership for two weeks. Click the button below to
+ sign up for the trial. After your trial period expires, you will be
+ automatically reset to the Basic plan, unless you upgrade to Pro.
+
Click on the PayPal button to pay for your Pro membership. Before you pay, please register for the free Basic membership and add your user name to the form.
+ Your payment will be valid for one year.
+ You will be taken to the secure PayPal payment site.
+
+
Recurring Payment
+
You need a Paypal account for this. This is plan will automatically renew each year.
+
+
+
+
One Year Subscription
+
Only a credit card needed. Will not automatically renew
+
+
+
+
+
Payment Processing
+
After you do the payment, we will manually change your membership to
+ "Pro". Depending on our availability, this may take some time
+ (typically one working day). Don't hesitate to contact us
+ if you have any questions at this stage.
+
+
If, for any reason, you are not happy with your Pro membership, please let me know through the contact form. I will contact you as soon as possible to discuss how we can make things better.
+
+
BrainTree Experimental Corner
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_help.html' %}
+{% endblock %}
+
+{% block scripts %}
+{% endblock %}
+
diff --git a/rowers/templates/promembership.html b/rowers/templates/promembership.html
index 201d3044..391d9f51 100644
--- a/rowers/templates/promembership.html
+++ b/rowers/templates/promembership.html
@@ -232,6 +232,7 @@
if you have any questions at this stage.
If, for any reason, you are not happy with your Pro membership, please let me know through the contact form. I will contact you as soon as possible to discuss how we can make things better.
+
diff --git a/rowers/urls.py b/rowers/urls.py
index 242c07b4..ee3aa1ce 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -437,6 +437,8 @@ urlpatterns = [
url(r'^analysis/$', views.analysis_view,name='analysis'),
url(r'^laboratory/$', views.laboratory_view,name='laboratory'),
url(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
+ url(r'^checkouts',views.checkouts_view,name='checkouts'),
+ url(r'^payments',views.payments_view,name='payments'),
url(r'^planrequired',views.planrequired_view),
url(r'^starttrial$',views.start_trial_view),
url(r'^startplantrial$',views.start_plantrial_view),
diff --git a/rowers/views.py b/rowers/views.py
index 8a32085c..de29e3f2 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -28,6 +28,7 @@ import isodate
import re
import cgi
from icalendar import Calendar, Event
+import braintree
from django.shortcuts import render
from django.template.loader import render_to_string
@@ -50,7 +51,7 @@ from rowers.forms import (
RaceResultFilterForm,PowerIntervalUpdateForm,FlexAxesForm,
FlexOptionsForm,DataFrameColumnsForm,OteWorkoutTypeForm,
MetricsForm,DisqualificationForm,disqualificationreasons,
- disqualifiers,SearchForm,
+ disqualifiers,SearchForm,BrainTreeForm
)
from django.core.urlresolvers import reverse, reverse_lazy
@@ -139,6 +140,7 @@ from rowsandall_app.settings import (
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
TP_CLIENT_ID,TP_REDIRECT_URI,TP_CLIENT_KEY,TP_CLIENT_SECRET,
+ BRAINTREE_MERCHANT_ID,BRAINTREE_PUBLIC_KEY,BRAINTREE_PRIVATE_KEY
)
from rowers.tasks_standalone import addcomment2
@@ -1027,6 +1029,87 @@ def add_defaultfavorites(r):
f.save()
return 1
+# Experimental - Payments
+@login_required()
+def payments_view(request):
+
+ r = getrequestrower(request)
+
+ gateway = braintree.BraintreeGateway(
+ braintree.Configuration(
+ braintree.Environment.Sandbox,
+ merchant_id=BRAINTREE_MERCHANT_ID,
+ public_key=BRAINTREE_PUBLIC_KEY,
+ private_key=BRAINTREE_PRIVATE_KEY,
+ )
+ )
+
+ # add code to store customer_id
+
+ client_token = gateway.client_token.generate({
+ "customer_id": r.id,
+ })
+
+ return render(request,
+ "payments.html",
+ {
+ 'client_token':client_token,
+ })
+
+
+@login_required()
+def checkouts_view(request):
+
+ r = getrequestrower(request)
+
+ if request.method != 'POST':
+ url = reverse(payments_view)
+ return HttpResponseRedirect(url)
+
+ # we're still here
+ gateway = braintree.BraintreeGateway(
+ braintree.Configuration(
+ braintree.Environment.Sandbox,
+ merchant_id=BRAINTREE_MERCHANT_ID,
+ public_key=BRAINTREE_PUBLIC_KEY,
+ private_key=BRAINTREE_PRIVATE_KEY,
+ )
+ )
+
+ form = BrainTreeForm(request.POST)
+ if form.is_valid():
+ nonce_from_the_client = form.cleaned_data['payment_method_nonce']
+ amount = form.cleaned_data['amount']
+ amount = str(amount)
+
+ result = gateway.transaction.sale({
+ "amount": amount,
+ "payment_method_nonce": nonce_from_the_client,
+ "options": {
+ "submit_for_settlement": True
+ }
+ })
+ if result.is_success:
+ transaction = result.transaction
+ amount = transaction.amount
+ messages.info(request,
+ "We have successfully received your payment of {amount} Euro".format(
+ amount=amount
+ )
+ )
+ else:
+ messages.error(request,"We are sorry but there was an error with the payment")
+ url = reverse(payments_view)
+ return HttpResponseRedirect(url)
+
+ else:
+ messages.error(request,"There was an error in the payment form")
+ url = reverse(payments_view)
+ return HttpResponseRedirect(url)
+
+ url = reverse(payments_view)
+ return HttpResponseRedirect(url)
+
# User registration
def rower_register_view(request):
diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py
index 93bfb4a4..61484bbf 100644
--- a/rowsandall_app/settings.py
+++ b/rowsandall_app/settings.py
@@ -421,3 +421,20 @@ try:
except KeyError:
workoutemailbox = 'workouts@rowsandall.com'
+
+# payments
+
+try:
+ BRAINTREE_MERCHANT_ID = CFG['braintree_merchant_id']
+except KeyError:
+ BRAINTREE_MERCHANT_ID = ''
+
+try:
+ BRAINTREE_PUBLIC_KEY = CFG['braintree_public_key']
+except KeyError:
+ BRAINTREE_PUBLIC_KEY = ''
+
+try:
+ BRAINTREE_PRIVATE_KEY = CFG['braintree_private_key']
+except KeyError:
+ BRAINTREE_PRIVATE_KEY = ''
From c48175bb4a5fd52678517748f6ed915af8baa137 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 17 Dec 2018 19:36:44 +0100
Subject: [PATCH 02/16] added customer id
---
rowers/models.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/rowers/models.py b/rowers/models.py
index e48f252a..3d3ee446 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -574,6 +574,9 @@ class Rower(models.Model):
gdproptin = models.BooleanField(default=False)
gdproptindate = models.DateTimeField(blank=True,null=True)
+ # customer data
+ customer_id = models.IntegerField(default=None,null=True,blank=True)
+
# Heart Rate Zone data
max = models.IntegerField(default=192,verbose_name="Max Heart Rate")
rest = models.IntegerField(default=48,verbose_name="Resting Heart Rate")
From 63a6114e0b1b35cd3f804d7c09eb848dfe0ec241 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 17 Dec 2018 22:39:11 +0100
Subject: [PATCH 03/16] trying to get country info
---
rowers/templates/payments.html | 3 ++-
rowers/views.py | 35 +++++++++++++++++++++++++++++++++-
2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/rowers/templates/payments.html b/rowers/templates/payments.html
index 4cc0b8f1..9c248bcb 100644
--- a/rowers/templates/payments.html
+++ b/rowers/templates/payments.html
@@ -235,7 +235,8 @@
+ By clicking on the link to stop the plan, you will downgrade to the Basic plan.
+ Future payments will be stopped.
+
+ {% else %}
+
+ You don't have any subscriptions or your subscriptions cannot be automatically stopped
+ from the site.
+
+
+ If you have paid through PayPal, log in to your PayPal account and cancel the recurring payment
+ there. We will manually downgrade your subscription.
+
+
+ If you have questions, don't hesitate to contact us.
+
+ {% endif %}
+
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_profile.html' %}
+{% endblock %}
+
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index e2329849..253ced99 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -125,7 +125,16 @@ def c2userid(user):
c2userid = c2stuff.get_userid(thetoken)
return c2userid
-
+
+@register.filter
+def currency(word):
+ try:
+ amount = float(word)
+ except ValueError:
+ return word
+
+ return '{amount:.2f}'.format(amount=amount)
+
@register.filter
def rkuserid(user):
try:
diff --git a/rowers/urls.py b/rowers/urls.py
index 63504a03..73bb2bd9 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -438,10 +438,13 @@ urlpatterns = [
url(r'^analysis/$', views.analysis_view,name='analysis'),
url(r'^laboratory/$', views.laboratory_view,name='laboratory'),
url(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
+ url(r'^checkout/(?P\d+)$',views.payment_confirm_view),
url(r'^billing',views.billing_view,name='billing'),
+ url(r'^paymentcompleted',views.payment_completed_view),
url(r'^paidplans',views.paidplans_view,name='paidplans'),
+ url(r'^me/cancelsubscriptions',views.plan_stop_view),
+ url(r'^me/cancelsubscription/(?P[\w\ ]+.*)$',views.plan_tobasic_view),
url(r'^checkouts',views.checkouts_view,name='checkouts'),
- url(r'^payments',views.payments_view,name='payments'),
url(r'^planrequired',views.planrequired_view),
url(r'^starttrial$',views.start_trial_view),
url(r'^startplantrial$',views.start_plantrial_view),
diff --git a/rowers/views.py b/rowers/views.py
index 5aba8d49..dda94c0d 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -84,7 +84,7 @@ from rowers.models import (
createmicrofillers, createmesofillers,
microcyclecheckdates,mesocyclecheckdates,macrocyclecheckdates,
TrainingMesoCycleForm, TrainingMicroCycleForm,
- RaceLogo,RowerBillingAddressForm,
+ RaceLogo,RowerBillingAddressForm,PaidPlan,
)
from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
@@ -738,8 +738,27 @@ def deactivate_user(request):
if request.method == "POST":
user_form = DeactivateUserForm(request.POST, instance=user)
if user_form.is_valid():
+ r = Rower.objects.get(user=user)
+ if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
+ try:
+ subscriptions = braintreestuff.find_subscriptions(r)
+ for subscription in subscriptions:
+ success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
+ for message in themessages:
+ messages.info(request,message)
+ except ProcessorCustomerError:
+ pass
+
+ r.paidplan = None
+ r.teamplanexpires = timezone.now()
+ r.planexpires = timezone.now()
+ r.clubsize = 0
+ r.rowerplan = 'basic'
+ r.save()
+
deactivate_user = user_form.save(commit=False)
user.is_active = False
+ user.save()
deactivate_user.save()
# url = reverse(auth_views.logout_then_login)
url = '/logout/?next=/login'
@@ -790,7 +809,17 @@ def remove_user(request):
name = user.first_name+' '+user.last_name
email = user.email
-
+ r = Rower.objects.get(user=user)
+ if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
+ try:
+ subscriptions = braintreestuff.find_subscriptions(r)
+ for subscription in subscriptions:
+ success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
+ for message in themessages:
+ messages.info(request,message)
+ except ProcessorCustomerError:
+ pass
+
if cd['delete_user']:
user.delete()
res = myqueue(queuehigh,
@@ -998,7 +1027,7 @@ def hasplannedsessions(user):
return result
-from rowers.utils import isprorower
+from rowers.utils import isprorower,ProcessorCustomerError
# Check if a user is a Pro member
def ispromember(user):
@@ -1056,6 +1085,19 @@ def billing_view(request):
if planselectform.is_valid():
plan = planselectform.cleaned_data['plan']
+ if billingaddressform.is_valid():
+ try:
+ customer_id = braintreestuff.create_customer(r)
+ except ProcessorCustomerError:
+ messages.error(request,"Something went wrong registering you as a customer.")
+ url = reverse(billing_view)
+ return HttpResponseRedirect(url)
+ url = reverse(payment_confirm_view,
+ kwargs={
+ 'planid':plan.id
+ })
+ return HttpResponseRedirect(url)
+
else:
billingaddressform = RowerBillingAddressForm(instance=r)
planselectform = PlanSelectForm(paymentprocessor='braintree')
@@ -1067,50 +1109,66 @@ def billing_view(request):
'planselectform':planselectform,
})
-
-
-# Experimental - Payments
@login_required()
-def payments_view(request):
+def plan_stop_view(request):
+ r = getrequestrower(request)
+
+ subscriptions = []
+
+ if r.paidplan is not None and r.paidplan.paymentprocessor == 'braintree':
+ try:
+ subscriptions = braintreestuff.find_subscriptions(r)
+ except ProcessorCustomerError:
+ r.paymentprocessor = None
+ r.save()
+
+
+
+ return render(request,
+ 'subscriptions_cancel.html',
+ {'rower':r,
+ 'subscriptions':subscriptions
+ })
+
+@login_required()
+def plan_tobasic_view(request,id=0):
+ r = getrequestrower(request)
+
+ if r.paidplan.paymentprocessor == 'braintree':
+ success, themessages,errormessages = braintreestuff.cancel_subscription(r,id)
+ for message in themessages:
+ messages.info(request,message)
+
+ for message in errormessages:
+ messages.error(request,message)
+
+ url = reverse(plan_stop_view)
+
+ return HttpResponseRedirect(url)
+
+
+
+@login_required()
+def payment_confirm_view(request,planid = 0):
+ try:
+ plan = PaidPlan.objects.get(id=planid)
+ except PaidPlan.DoesNotExist:
+ messages.error(request,"Something went wrong. Please try again.")
+ url = reverse(billing_view)
+ return HttpResponseRedirect(url)
r = getrequestrower(request)
- gateway = braintree.BraintreeGateway(
- braintree.Configuration(
- braintree.Environment.Sandbox,
- merchant_id=BRAINTREE_MERCHANT_ID,
- public_key=BRAINTREE_PUBLIC_KEY,
- private_key=BRAINTREE_PRIVATE_KEY,
- )
- )
-
- # add code to store customer_id
- if not r.customer_id:
- result = gateway.customer.create(
- {
- 'first_name':r.user.first_name,
- 'last_name':r.user.last_name,
- 'email':r.user.email,
- })
-
- if not result.is_success:
- messages.error(request,'Failed to create customer. Please try again later')
- return render(request,
- "payments.html")
- else:
- r.customer_id = result.customer.id
- r.save()
-
- client_token = gateway.client_token.generate({
- "customer_id": r.customer_id,
- })
+ client_token = braintreestuff.get_client_token(r)
return render(request,
- "payments.html",
- {
+ "paymentconfirm.html",
+ {
+ 'plan':plan,
'client_token':client_token,
+ 'rower':r,
})
-
+
@login_required()
def checkouts_view(request):
@@ -1118,72 +1176,41 @@ def checkouts_view(request):
r = getrequestrower(request)
if request.method != 'POST':
- url = reverse(payments_view)
+ url = reverse(paidplans_view)
return HttpResponseRedirect(url)
- # we're still here
- gateway = braintree.BraintreeGateway(
- braintree.Configuration(
- braintree.Environment.Sandbox,
- merchant_id=BRAINTREE_MERCHANT_ID,
- public_key=BRAINTREE_PUBLIC_KEY,
- private_key=BRAINTREE_PRIVATE_KEY,
- )
- )
-
form = BillingForm(request.POST)
if form.is_valid():
- nonce_from_the_client = form.cleaned_data['payment_method_nonce']
- amount = form.cleaned_data['amount']
- amount = str(amount)
-
- #for testing
- #nonce_from_the_client = 'fake-processor-declined-visa-none'
-
- result = gateway.transaction.sale({
- "amount": amount,
- "payment_method_nonce": nonce_from_the_client,
- "options": {
- "submit_for_settlement": True
- }
- })
- if result.is_success:
- transaction = result.transaction
- if transaction.payment_instrument_type == "credit_card":
- country = transaction.credit_card_details.country_of_issuance
- if country == 'Unknown':
- bin = transaction.credit_card_details.bin
- url = "https://lookup.binlist.net/"+str(bin)
- headers = {
- 'Accept-Version':'3',
- }
- binresult = requests.get(url,headers=headers)
- print binresult.status_code
- if binresult.status_code == 200:
- js = binresult.json()
- country = js['country']['name']
-
- print country
- amount = transaction.amount
- messages.info(request,
- "We have successfully received your payment of {amount} Euro".format(
- amount=amount
- )
- )
- else:
- messages.error(request,"We are sorry but there was an error with the payment")
- url = reverse(payments_view)
+ data = form.cleaned_data
+ success = braintreestuff.create_subscription(r,data)
+ if success:
+ messages.info(request,"Your payment has succeeded and your plan has been updated")
+ url = reverse(payment_completed_view)
return HttpResponseRedirect(url)
-
+ else:
+ messages.error(request,"There was a problem with your payment")
+ url = reverse(billing_view)
+ return HttpResponseRedirect(url)
+
else:
messages.error(request,"There was an error in the payment form")
- url = reverse(payments_view)
+ url = reverse(billing_view)
return HttpResponseRedirect(url)
url = reverse(payments_view)
return HttpResponseRedirect(url)
+@login_required()
+def payment_completed_view(request):
+ r = getrequestrower(request)
+
+ return render(request,
+ "payment_completed.html",
+ {
+ 'rower':r
+ })
+
# User registration
def rower_register_view(request):
From a0a03a90dff3071b41edce07068c7fd67dd5048b Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 19 Dec 2018 21:51:39 +0100
Subject: [PATCH 09/16] added subscription_id
---
rowers/admin.py | 2 +-
rowers/braintreestuff.py | 1 +
rowers/models.py | 6 ++++--
rowers/payments.py | 2 +-
rowers/templates/paidplans.html | 12 ++++++++++++
5 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/rowers/admin.py b/rowers/admin.py
index f5907059..5be9aa7f 100644
--- a/rowers/admin.py
+++ b/rowers/admin.py
@@ -124,7 +124,7 @@ class VirtualRaceResultAdmin(admin.ModelAdmin):
search_fields = ['race__name','username']
class PaidPlanAdmin(admin.ModelAdmin):
- list_display = ('name','shortname','price','paymenttype','paymentprocessor','clubsize')
+ list_display = ('name','shortname','price','paymenttype','paymentprocessor','clubsize','external_id')
admin.site.unregister(User)
admin.site.register(User,UserAdmin)
diff --git a/rowers/braintreestuff.py b/rowers/braintreestuff.py
index fb1f22a8..1410476b 100644
--- a/rowers/braintreestuff.py
+++ b/rowers/braintreestuff.py
@@ -114,6 +114,7 @@ def create_subscription(rower,data):
rower.clubsize = plan.clubsize
rower.paymenttype = plan.paymenttype
rower.rowerplan = plan.shortname
+ rower.subscription_id = result.subscription.id
rower.save()
return True
else:
diff --git a/rowers/models.py b/rowers/models.py
index 766c9caf..ab8f822f 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -571,7 +571,7 @@ class PaidPlan(models.Model):
clubsize = models.IntegerField(default=0)
def __unicode__(self):
- return '{name} - {shortname} at {price} EURO ({paymenttype} payment)'.format(
+ return '{name} - {shortname} at {price:.2f} EURO ({paymenttype} payment)'.format(
name = self.name,
shortname = self.shortname,
price = self.price,
@@ -623,6 +623,8 @@ class Rower(models.Model):
postal_code = models.CharField(default='',blank=True,null=True,max_length=200)
customer_id = models.CharField(default=None,null=True,blank=True,max_length=200)
+ subscription_id = models.CharField(default=None,null=True,
+ blank=True,max_length=200)
rowerplan = models.CharField(default='basic',max_length=30,
choices=plans)
@@ -634,7 +636,7 @@ class Rower(models.Model):
paymentprocessor = models.CharField(max_length=50,
choices=paymentprocessors,
null=True,blank=True,
- default=None)
+ default='braintree')
paidplan = models.ForeignKey(PaidPlan,null=True,default=None)
diff --git a/rowers/payments.py b/rowers/payments.py
index 692852af..488191c1 100644
--- a/rowers/payments.py
+++ b/rowers/payments.py
@@ -22,7 +22,7 @@ def setrowerplans():
if paidplans:
r.paidplan = paidplans[0]
- r.paymenttype = 'paypal'
+ r.paymentprocessor = 'paypal'
r.save()
else:
print 'Could not set plan for ',r
diff --git a/rowers/templates/paidplans.html b/rowers/templates/paidplans.html
index 1c3c30e4..bbd4789a 100644
--- a/rowers/templates/paidplans.html
+++ b/rowers/templates/paidplans.html
@@ -241,6 +241,18 @@
{% endif %}
+ {% elif rower and rower.rowerplan == 'coach' and rower.clubsize < 100 %}
+
{{ user.rower.country }}
diff --git a/rowers/templates/upgrade.html b/rowers/templates/upgrade.html
new file mode 100644
index 00000000..42b04f13
--- /dev/null
+++ b/rowers/templates/upgrade.html
@@ -0,0 +1,49 @@
+{% extends "newbase.html" %}
+{% block title %}Rowsandall Paid Membership{% endblock title %}
+{% load rowerfilters %}
+{% block main %}
+
+
Upgrade
+
+
+
+
+
Billing Details
+
For tax reasons, we need your country of residence. You should
+ update this when it is incorrect.
+
+ {{ billingaddressform.as_table }}
+
+
+
+
Choose your Plan
+
+ {{ planselectform.as_table }}
+
+
+
+
+ Your upgrade will be effective immediately. For the current billing
+ cycle, you will be charged for a prorated amount. For example, when
+ you upgrade from a 15€ plan to a 65€ plan (a difference of
+ 50€) in the 6th month of the 12 month billing cycle, you
+ will be charged 35€.
+
+ Your upgrade will be effective immediately. For the current billing
+ cycle, you will be charged for a prorated amount. For example, when
+ you upgrade from a 15€ plan to a 65€ plan (a difference of
+ 50€) in the 6th month of the 12 month billing cycle, you
+ will be charged 35€.
+
diff --git a/rowers/templates/subscription_create_email.html b/rowers/templates/subscription_create_email.html
new file mode 100644
index 00000000..c312a6b1
--- /dev/null
+++ b/rowers/templates/subscription_create_email.html
@@ -0,0 +1,60 @@
+{% extends "emailbase.html" %}
+
+{% block body %}
+
Dear {{ name }},
+
+
+ Thank you. We have received the payment of € {{ amount }} for your new
+ subscription to the Rowsandall paid plan "{{ planname }}".
+
+
+{% if recurring %}
+
+ Your next charge is due on {{ end_of_billing_period }}. We will charge your {{ paymentmethod }}
+ on that date.
+
+
+
+ The subscription will keep running until you change or stop it. At any point in time you
+ can change the automatically renewing subscription to a "one year only" subscription through
+ the upgrade page. On this page, you can also
+ upgrade your subscription.
+
+
+{% else %}
+
+ This one year subscription will automatically end on {{ end_of_billing_period }}. You can
+ renew your subscription after that.
+
+
+
+ At any point in time, you can change your subscription to an automatically renewing subscription.
+ You can do this on the upgrade page.
+ Here, you can also upgrade your subscription.
+
+
+{% endif %}
+
+
+ Upgrades in the middle of a billing cycle will be charged pro-rated. For the current billing
+ cycle, you will only be charged for the price difference for the remaining fraction of the
+ billing cycle. If you downgrade to a lower cost subscription, the pro-rated difference will be
+ used as a credit, lowering the amount charged on the next billing cycle.
+
+
+
+ You can stop the subscription through
+ the subscription management page. The
+ subscription will be stopped immediately without a refund.
+
+
+
+ Please contact our customer service by replying to this email if you have any further
+ questions regarding your subscription.
+
+ Thank you. We have received the payment of € {{ amount }} for
+ your updated Rowsandall subscription.
+ You are now on the Rowsandall paid plan "{{ planname }}".
+
+
+{% if recurring %}
+
+ The subscription cost is €{{ price }} per year.
+ Your next charge is due on {{ end_of_billing_period }}. We will charge your {{ paymentmethod }}
+ on that date.
+
+
+
+ The subscription will keep running until you change or stop it. At any point in time you
+ can change the automatically renewing subscription to a "one year only" subscription through
+ the upgrade page. On this page, you can also
+ upgrade your subscription.
+
+
+{% else %}
+
+ The price of the subscription is €{{ price }}. You have paid €{{ amount }} as a
+ prorated cost of your upgrade.
+ This one year subscription will automatically end on {{ end_of_billing_period }}. You can
+ renew your subscription after that.
+
+
+
+ At any point in time, you can change your subscription to an automatically renewing subscription.
+ You can do this on the upgrade page.
+ Here, you can also upgrade your subscription.
+
+{% endif %}
+
+
+ Upgrades in the middle of a billing cycle are charged pro-rated. For the current billing
+ cycle, you have only been charged for the price difference for the remaining fraction of the
+ billing cycle. If you downgraded to a lower cost subscription, the pro-rated difference will be
+ used as a credit, lowering the amount charged on the next billing cycle.
+
+
+
+ You can stop the subscription through
+ the subscription management page. The
+ subscription will be stopped immediately without a refund.
+
+
+
+ Please contact our customer service by replying to this email if you have any further
+ questions regarding your subscription.
+
The subscription cost is €{{ price }} per year.
- Your next charge is due on {{ end_of_billing_period }}. We will charge your {{ paymentmethod }}
+ Your next charge is due on {{ end_of_billing_period }}. We will charge you automatically
on that date.
diff --git a/rowers/templates/subscription_update_notification.html b/rowers/templates/subscription_update_notification.html
new file mode 100644
index 00000000..1df49f73
--- /dev/null
+++ b/rowers/templates/subscription_update_notification.html
@@ -0,0 +1,30 @@
+{% extends "emailbase.html" %}
+
+{% block body %}
+
User {{ name }} has updated his subscription.
+
+
+ New plan: "{{ planname }}".
+
+
+{% if recurring %}
+
+ The subscription cost is €{{ price }} per year.
+ The next charge is due on {{ end_of_billing_period }}. on that date.
+
+{% else %}
+
+ The subscription cost is €{{ price }}. The subscription ends on {{ end_of_billing_period }}
+
For tax reasons, we need your country of residence. You should
+ update this when it is incorrect.
+
+ {{ billingaddressform.as_table }}
+
+
+
+
Choose your Plan
+
+ {{ planselectform.as_table }}
+
+
+
+
+ Your downgrade will be effective immediately.
+ The price difference for the current billing cycle will
+ be credited to your next charge (prorated). For example,
+ when you downgrade from a 65€ plan to a 15€ plan
+ (50€ difference), in the 6th month of the 12 month
+ billing cycle, you will have a credit of 25€ which
+ will be used for the next billing cycles.
+
+ {% csrf_token %}
+
+ You will be able to review your order before purchase.
+
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_profile.html' %}
+{% endblock %}
+
diff --git a/rowers/templates/downgrade_completed.html b/rowers/templates/downgrade_completed.html
new file mode 100644
index 00000000..1fdde09b
--- /dev/null
+++ b/rowers/templates/downgrade_completed.html
@@ -0,0 +1,26 @@
+{% extends "newbase.html" %}
+{% block title %}Rowsandall Paid Membership{% endblock title %}
+{% load rowerfilters %}
+{% block main %}
+
+
Your Change was completed
+
+
+ Thank you for changing to {{ user.rower.paidplan.name }}. You're all settled.
+ membership.
+
+
+
+ {% if user.rower.paymenttype == 'recurring' %}
+ Your next payment will be automatically processed on {{ user.rower.planexpires }}
+ {% else %}
+ Your plan will end automatically on {{ user.rower.planexpires }}
+ {% endif %}
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_profile.html' %}
+{% endblock %}
+
diff --git a/rowers/templates/downgradeconfirm.html b/rowers/templates/downgradeconfirm.html
new file mode 100644
index 00000000..c5bb79b2
--- /dev/null
+++ b/rowers/templates/downgradeconfirm.html
@@ -0,0 +1,118 @@
+{% extends "newbase.html" %}
+{% block title %}Rowsandall Paid Membership{% endblock title %}
+{% load rowerfilters %}
+{% block main %}
+
+
+ Thank you. You have successfully changed your plan to "{{ planname }}".
+
+
+{% if recurring %}
+
+ The subscription cost is €{{ price }} per year.
+ Your next charge is due on {{ end_of_billing_period }}. We will charge you automatically
+ on that date. Because you downgraded, you have a credit on your account which will be
+ used before charging your payment method.
+
+
+
+ The subscription will keep running until you change or stop it. At any point in time you
+ can change the automatically renewing subscription to a "one year only" subscription through
+ the upgrade page. On this page, you can also
+ upgrade your subscription.
+
+
+{% else %}
+
+ The price of the subscription is €{{ price }}.
+ This one year subscription will automatically end on {{ end_of_billing_period }}. You can
+ renew your subscription after that. Because you downgraded, you have a credit on your
+ account which will be used for future subscriptions or upgrades.
+
+
+
+ At any point in time, you can change your subscription to an automatically renewing subscription.
+ You can do this on the upgrade page.
+ Here, you can also upgrade your subscription.
+
+{% endif %}
+
+
+ Upgrades in the middle of a billing cycle are charged pro-rated. For the current billing
+ cycle, you have only been charged for the price difference for the remaining fraction of the
+ billing cycle. If you downgraded to a lower cost subscription, the pro-rated difference will be
+ used as a credit, lowering the amount charged on the next billing cycle.
+
+
+
+ You can stop the subscription through
+ the subscription management page. The
+ subscription will be stopped immediately without a refund.
+
+
+
+ Please contact our customer service by replying to this email if you have any further
+ questions regarding your subscription.
+