diff --git a/rowers/braintreestuff.py b/rowers/braintreestuff.py
index 7efd6345..fb1f22a8 100644
--- a/rowers/braintreestuff.py
+++ b/rowers/braintreestuff.py
@@ -1,4 +1,6 @@
import braintree
+from django.utils import timezone
+import datetime
from rowsandall_app.settings import (
BRAINTREE_MERCHANT_ID,BRAINTREE_PUBLIC_KEY,BRAINTREE_PRIVATE_KEY
@@ -16,8 +18,8 @@ gateway = braintree.BraintreeGateway(
from rowers.models import Rower,PaidPlan
from rowers.utils import ProcessorCustomerError
-def create_customer(rower):
- if not rower.customer_id:
+def create_customer(rower,force=False):
+ if not rower.customer_id or force:
result = gateway.customer.create(
{
'first_name':rower.user.first_name,
@@ -28,14 +30,25 @@ def create_customer(rower):
raise ProcessorCustomerError
else:
rower.customer_id = result.customer.id
+ rower.paymentprocessor = 'braintree'
rower.save()
+ return rower.customer_id
else:
return rower.customer_id
+
+
def get_client_token(rower):
- client_token = gateway.client_token.generate({
- "customer_id":rower.customer_id,
+ try:
+ client_token = gateway.client_token.generate({
+ "customer_id":rower.customer_id,
})
+ except ValueError:
+ customer_id = create_customer(rower,force=True)
+
+ client_token = gateway.client_token.generate({
+ "customer_id": customer_id,
+ })
return client_token
@@ -51,3 +64,125 @@ def get_plans_costs():
plan.save()
return plans
+
+def make_payment(rower,data):
+ nonce_from_the_client = data['payment_method_nonce']
+ amount = 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
+
+ return amount
+ else:
+ return 0,''
+
+def create_subscription(rower,data):
+ planid = data['plan']
+ plan = PaidPlan.objects.get(id=planid)
+ nonce_from_the_client = data['payment_method_nonce']
+ amount = data['amount']
+
+ # create or find payment method
+ result = gateway.payment_method.create({
+ "customer_id": rower.customer_id,
+ "payment_method_nonce": nonce_from_the_client
+ })
+
+ if result.is_success:
+ payment_method_token = result.payment_method.token
+ else:
+ return False
+
+ result = gateway.subscription.create({
+ "payment_method_token": payment_method_token,
+ "plan_id": plan.external_id
+ })
+
+ if result.is_success:
+ rower.paidplan = plan
+ rower.planexpires = timezone.now()+datetime.timedelta(days=365)
+ rower.teamplanexpires = timezone.now()+datetime.timedelta(days=365)
+ rower.clubsize = plan.clubsize
+ rower.paymenttype = plan.paymenttype
+ rower.rowerplan = plan.shortname
+ rower.save()
+ return True
+ else:
+ return False
+
+
+ return False
+
+def cancel_subscription(rower,id):
+ themessages = []
+ errormessages = []
+ try:
+ result = gateway.subscription.cancel(id)
+ messages.append("Subscription canceled")
+ except:
+ errormessages.append("We could not find the subscription record in our customer database")
+ return False, themessages, errormessages
+
+ rower.paidplan = None
+ rower.teamplanexpires = timezone.now()
+ rower.planexpires = timezone.now()
+ rower.clubsize = 0
+ rower.rowerplan = 'basic'
+ rower.save()
+ themessages.append("Your plan was reset to basic")
+
+ return True, themessages,errormessages
+
+
+def find_subscriptions(rower):
+ try:
+ result = gateway.customer.find(rower.customer_id)
+ except:
+ raise ProcessorCustomerError("We could not find the customer in the database")
+
+ active_subscriptions = []
+
+ cards = result.credit_cards
+ for card in cards:
+ for subscription in card.subscriptions:
+ if subscription.status == 'Active':
+ active_subscriptions.append(subscription)
+
+ try:
+ paypal_accounts = result.paypal_accounts
+ for account in accuonts:
+ for subscription in account.subscriptions:
+ if subscription.status == 'Active':
+ active_subscriptions.append(subscription)
+ except AttributeError:
+ pass
+
+ result = []
+
+ for subscription in active_subscriptions:
+
+ plan = PaidPlan.objects.filter(paymentprocessor="braintree",
+ external_id=subscription.plan_id)[0]
+
+ thedict = {
+ 'end_date': subscription.billing_period_end_date,
+ 'plan_id': subscription.plan_id,
+ 'price': subscription.price,
+ 'id': subscription.id,
+ 'plan': plan.name
+ }
+
+ result.append(thedict)
+
+ return result
+
+
diff --git a/rowers/forms.py b/rowers/forms.py
index b4f03bb9..c7a35b9d 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -22,7 +22,8 @@ from metrics import axes
# BillingForm form
class BillingForm(forms.Form):
amount = forms.FloatField(required=True)
- payment_method_nonce = forms.CharField(max_length=255)
+ plan = forms.IntegerField(widget=forms.HiddenInput())
+ payment_method_nonce = forms.CharField(max_length=255,required=True)
# login form
diff --git a/rowers/models.py b/rowers/models.py
index 50fb9cc1..766c9caf 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -558,7 +558,7 @@ paymentprocessors = (
class PaidPlan(models.Model):
shortname = models.CharField(max_length=50,choices=plans)
name = models.CharField(max_length=200)
- external_id = models.IntegerField(blank=True,null=True,default=None)
+ external_id = models.CharField(blank=True,null=True,default=None,max_length=200)
price = models.FloatField(blank=True,null=True,default=None)
paymentprocessor = models.CharField(
max_length=50,choices=paymentprocessors,default='braintree')
@@ -633,7 +633,8 @@ class Rower(models.Model):
)
paymentprocessor = models.CharField(max_length=50,
choices=paymentprocessors,
- default='braintree')
+ null=True,blank=True,
+ default=None)
paidplan = models.ForeignKey(PaidPlan,null=True,default=None)
diff --git a/rowers/payments.py b/rowers/payments.py
index ecc5691d..692852af 100644
--- a/rowers/payments.py
+++ b/rowers/payments.py
@@ -28,7 +28,7 @@ def setrowerplans():
print 'Could not set plan for ',r
def is_existing_customer(rower):
- if rower.country is not None and rower.customer_id is not None and r.country != '':
+ if rower.country is not None and rower.customer_id is not None and rower.country != '':
return True
return False
diff --git a/rowers/templates/billing.html b/rowers/templates/billing.html
index ea725daa..bf4a358f 100644
--- a/rowers/templates/billing.html
+++ b/rowers/templates/billing.html
@@ -31,6 +31,6 @@
{% endblock %}
{% block sidebar %}
-{% include 'menu_help.html' %}
+{% include 'menu_profile.html' %}
{% endblock %}
diff --git a/rowers/templates/payment_completed.html b/rowers/templates/payment_completed.html
new file mode 100644
index 00000000..b29fbaea
--- /dev/null
+++ b/rowers/templates/payment_completed.html
@@ -0,0 +1,26 @@
+{% extends "newbase.html" %}
+{% block title %}Rowsandall Paid Membership{% endblock title %}
+{% load rowerfilters %}
+{% block main %}
+
+
Your Payment was completed
+
+
+ Thank you for registering to {{ user.rower.paidplan.name }}. You have paid for 12 months
+ 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/paymentconfirm.html b/rowers/templates/paymentconfirm.html
new file mode 100644
index 00000000..4bb14355
--- /dev/null
+++ b/rowers/templates/paymentconfirm.html
@@ -0,0 +1,113 @@
+{% extends "newbase.html" %}
+{% block title %}Rowsandall Paid Membership{% endblock title %}
+{% load rowerfilters %}
+{% block main %}
+
+
+ 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):