Private
Public Access
1
0

works but cannot change address

This commit is contained in:
Sander Roosendaal
2021-03-24 06:35:25 +01:00
parent 9a06a8902f
commit a9091bfed7
12 changed files with 419 additions and 10 deletions

View File

@@ -183,7 +183,7 @@ def make_payment(rower,data):
return False,0 return False,0
amount = data['amount'] amount = data['amount']
amount = '{amount:.f2}'.format(amount=amount) amount = '{amount}'.format(amount=amount)
result = gateway.transaction.sale({ result = gateway.transaction.sale({
"amount": amount, "amount": amount,
@@ -200,13 +200,18 @@ def make_payment(rower,data):
l = rower.user.last_name, l = rower.user.last_name,
) )
fakturoid_contact_id = fakturoid.get_contacts(rower)
if not fakturoid_contact_id:
fakturoid_contact_id = fakturoid.create_contact(rower)
id = fakturoid.create_invoice(rower,amount,transaction.id,dosend=True,contact_id=fakturoid_contact_id,
name='Rowsandall Purchase')
job = myqueue(queuehigh,handle_send_email_transaction, job = myqueue(queuehigh,handle_send_email_transaction,
name, rower.user.email, amount) name, rower.user.email, amount)
return amount,'' return amount,True
else: else:
return 0,'' return 0,False
def update_subscription(rower,data,method='up'): def update_subscription(rower,data,method='up'):
planid = data['plan'] planid = data['plan']

View File

@@ -83,11 +83,14 @@ def create_contact(rower):
# this should be triggered by a Braintree webhook # this should be triggered by a Braintree webhook
def create_invoice(rower,amount,braintreeid,dosend=True, def create_invoice(rower,amount,braintreeid,dosend=True,
contact_id=None): contact_id=None,name=None):
if not contact_id: if not contact_id:
contact_id = get_contacts(rower) contact_id = get_contacts(rower)
if not name:
name = 'Rowsandall Subscription'
with open('braintreewebhooks.log','a') as f: with open('braintreewebhooks.log','a') as f:
f.write('Creating invoice for contact iD '+str(contact_id)+'\n') f.write('Creating invoice for contact iD '+str(contact_id)+'\n')
@@ -104,7 +107,7 @@ def create_invoice(rower,amount,braintreeid,dosend=True,
'paid_amount': str(amount), 'paid_amount': str(amount),
'status': 'paid', 'status': 'paid',
'lines': [{ 'lines': [{
'name': 'Rowsandall Subscription', 'name': name,
'quantity': '1', 'quantity': '1',
'unit_price': str(amount), 'unit_price': str(amount),
'vat_rate': 0, 'vat_rate': 0,

View File

@@ -101,6 +101,19 @@ class BillingForm(forms.Form):
paymenttype = forms.CharField(max_length=255,required=True) paymenttype = forms.CharField(max_length=255,required=True)
tac= forms.BooleanField(required=True,initial=False) tac= forms.BooleanField(required=True,initial=False)
# TrainingPlanBillingForm form
class TrainingPlanBillingForm(forms.Form):
amount = FlexibleDecimalField(required=True,decimal_places=2,
max_digits=8)
plan = forms.IntegerField(widget=forms.HiddenInput())
payment_method_nonce = forms.CharField(max_length=255,required=True)
paymenttype = forms.CharField(max_length=255,required=True)
enddate = forms.DateField(widget=forms.HiddenInput)
name = forms.CharField(max_length=255,required=False)
notes = forms.CharField(max_length=255,required=True)
status = forms.CharField(max_length=255,required=True)
tac= forms.BooleanField(required=True,initial=False)
# login form # login form
class LoginForm(forms.Form): class LoginForm(forms.Form):

View File

@@ -1612,7 +1612,7 @@ class TrainingPlan(models.Model):
rowers = models.ManyToManyField(Rower,related_name='planathletes', rowers = models.ManyToManyField(Rower,related_name='planathletes',
verbose_name='Athletes') verbose_name='Athletes')
manager = models.ForeignKey(Rower,related_name='planmanager',null=True,on_delete=models.SET_NULL) manager = models.ForeignKey(Rower,related_name='planmanager',null=True,on_delete=models.SET_NULL)
name = models.CharField(max_length=150,blank=True) name = models.CharField(max_length=150,blank=True, verbose_name="Plan Name")
status = models.BooleanField(default=True,verbose_name='Active') status = models.BooleanField(default=True,verbose_name='Active')
target = models.ForeignKey(TrainingTarget,blank=True,null=True,on_delete=models.SET_NULL) target = models.ForeignKey(TrainingTarget,blank=True,null=True,on_delete=models.SET_NULL)
startdate = models.DateField(default=current_day) startdate = models.DateField(default=current_day)

View File

@@ -0,0 +1,43 @@
{% extends "newbase.html" %}
{% block title %}Rowsandall Purchase Training Plan{% endblock title %}
{% load rowerfilters %}
{% block main %}
<h1>Purchase Training Plan</h1>
<form action="" method="post">
<ul class="main-content">
<li class="grid_3">
<h2>Billing Details</h2>
<p>For tax reasons, we need your country of residence. You should
update this when it is incorrect.</p>
<table>
{{ form.as_table }}
{{ billingaddressform.as_table }}
</table>
</li>
<li class="grid_3">
<h2>Your Purchase</h2>
<p>Plan: {{ plan.name }}</p>
<p>Price: {{ plan.price }}&euro;</p>
</li>
<li>
<p>
Your purchase will be effective immediately. You will be charged
for the price of the plan.
</p>
</li>
<li class="grid_3">
{% csrf_token %}
<input type="submit" value="Proceed">
You will be able to review your order before finalizing your purchase.
</li>
</ul>
</form>
{% endblock %}
{% block sidebar %}
{% include 'menu_payments.html' %}
{% endblock %}

View File

@@ -0,0 +1,114 @@
{% extends "newbase.html" %}
{% block title %}Rowsandall Purchase Training Plan{% endblock title %}
{% load rowerfilters %}
{% block main %}
<div id="paymenterror">
</div>
<h1>Confirm Your Payment</h1>
<h2>Order Overview</h2>
<p>
Please refer to our <a href="/rowers/legal/">terms and conditions</a> for our
payments and refunds policy. Accepted payment methods are the payment methods offered
by
<a href="https://www.braintreegateway.com/merchants/jytq7yxsm66qqdzb/verified">Braintree</a>
through us. If you have any questions about our payments and refunds policy, please contact
us by email at support@rowsandall.com.
</p>
<p>
Payments will be processed by Braintree (A PayPal service):
</p>
<p>
<a href="https://www.braintreegateway.com/merchants/{{ BRAINTREE_MERCHANT_ID }}/verified" target="_blank">
<img src="https://s3.amazonaws.com/braintree-badges/braintree-badge-light.png" width="164px" height ="44px" border="0"/>
</a>
</p>
<ul class="main-content">
<li class="grid_2">
<p>
<table class="plantable shortpadded" width="80%">
<tbody>
<tr>
<th>Plan</th><td>{{ plan.name }}</td>
</tr>
<tr>
<th>Total</th><td>&euro; {{ plan.price|currency }}
</td>
</tr>
</tbody>
</table>
</p>
<p>
<table class="plantable shortpadded" width="80%">
<tbody>
<tr>
<th>Street Address</th><td>{{ user.rower.street_address }}</td>
</tr>
<tr>
<th>City</th><td>{{ user.rower.city }}</td>
</tr>
<tr>
<th>Postal Code</th><td>{{ user.rower.postal_code }}</td>
</tr>
<tr>
<th>Country</th><td>{{ user.rower.country }}
</td>
</tr>
</tbody>
</table>
</p>
</li>
<li class="grid_4">
<form id="payment-form" method="post" action="/rowers/purchasecheckouts/"
autocomplete="off">
<section>
<label for="amount">
<div class="input-wrapper amount-wrapper">
<input id="amount" name="amount" type="hidden" min="1" placeholder="Amount"
value="{{ plan.price }}" readonly>
</div>
</label>
<div class="bt-drop-in-wrapper">
<div id="bt-dropin"></div>
</div>
</section>
<div id="paymenterror2"> </div>
<input type="hidden" id="nonce" name="payment_method_nonce" />
<input type="hidden" id="paymenttype" name="paymenttype" />
<input type="hidden" id="plan" name="plan" value="{{ plan.id }}">
<input type="hidden" id="status" name="status" value="{{ status }}">
<input type="hidden" id="notes" name="notes" value="{{ notes }}">
<input type="hidden" id="name" name="name" value="{{ name }}">
<input type="hidden" id="enddate" name="enddate" value="{{ enddate }}">
<p>
<input id="tac" type="checkbox" name="tac" value="tac">I have taken note of the
<a href="/rowers/legal/#refunds" target="_blank">Refund and Cancellation</a>
Policy and agree with the <a href="/rowers/legal/" target="_blank">Terms of Service</a>.
</p>
{% csrf_token %}
<button type="submit" id="submit-button"><span>Purchase for &euro; {{ plan.price|currency }}</span></button>
</form>
</li>
<li class="grid_4">
<p>
After you hit the Purchase button, the transaction will be launched.
Please wait until the transaction completes. Do not click the
button twice. Do not close your browser window.
</p>
</li>
</ul>
{% include 'braintreedropin.html' %}
{% endblock %}
{% block sidebar %}
{% include 'menu_payments.html' %}
{% endblock %}

View File

@@ -27,6 +27,11 @@
<p>What the plan will achieve: {{ plan.target }}</p> <p>What the plan will achieve: {{ plan.target }}</p>
{% endif %} {% endif %}
<p>Weekly volume: {{ plan.hoursperweek }} hours per week over {{ plan.sessionsperweek }} sessions.</p> <p>Weekly volume: {{ plan.hoursperweek }} hours per week over {{ plan.sessionsperweek }} sessions.</p>
{% if plan.price == 0 %}
<p>Price: Free</p>
{% else %}
<p>Price: {{ plan.price }}&euro;</p>
{% endif %}
</li> </li>
<li class="grid_2"> <li class="grid_2">
<p> <p>
@@ -37,12 +42,20 @@
You can select the end date manually or use the training target (if you have any), and the plan will start at You can select the end date manually or use the training target (if you have any), and the plan will start at
the date it needs to complete in time. the date it needs to complete in time.
</p> </p>
{% if plan.price == 0 %}
<form enctype="multipart/form-data" action="" method="post"> <form enctype="multipart/form-data" action="" method="post">
{% else %}
<form enctype="multipart/form-data" action="/rowers/buyplan/{{ plan.id }}/" method="post">
{% endif %}
<table> <table>
{{ form.as_table }} {{ form.as_table }}
</table> </table>
{% csrf_token %} {% csrf_token %}
{% if plan.price == 0 %}
<p><input class="button" type="submit" value="Create Plan and Add Sessions"></p> <p><input class="button" type="submit" value="Create Plan and Add Sessions"></p>
{% else %}
<p><input class="button" type="submit" action="/rowers/buyplan/{{ plan.id }}/" value="BUY NOW and Add Sessions"></p>
{% endif %}
</form> </form>
</li> </li>
<li class="grid_4"> <li class="grid_4">

View File

@@ -22,6 +22,11 @@
<p>Goal: {{ plan.goal }}</p> <p>Goal: {{ plan.goal }}</p>
{% endif %} {% endif %}
<p>{{ plan.hoursperweek }} hours per week over {{ plan.sessionsperweek }} sessions</p> <p>{{ plan.hoursperweek }} hours per week over {{ plan.sessionsperweek }} sessions</p>
{% if plan.price == 0 %}
<p>Price: Free</p>
{% else %}
<p>Price: {{ plan.price }}&euro;</p>
{% endif %}
</li> </li>
{% endfor %} {% endfor %}

View File

@@ -719,7 +719,8 @@ urlpatterns = [
re_path(r'^me/cancelsubscription/(?P<id>[\w\ ]+.*)/$',views.plan_tobasic_view,name='plan_tobasic_view'), re_path(r'^me/cancelsubscription/(?P<id>[\w\ ]+.*)/$',views.plan_tobasic_view,name='plan_tobasic_view'),
re_path(r'^checkouts/$',views.checkouts_view,name='checkouts'), re_path(r'^checkouts/$',views.checkouts_view,name='checkouts'),
re_path(r'^upgradecheckouts/$',views.upgrade_checkouts_view,name='upgrade_checkouts'), re_path(r'^upgradecheckouts/$',views.upgrade_checkouts_view,name='upgrade_checkouts'),
re_path(r'^downgradecheckouts/$',views.downgrade_checkouts_view,name='downgrade_checkouts'), re_path(r'^upgradecheckouts/$',views.upgrade_checkouts_view,name='upgrade_checkouts'),
re_path(r'^purchasecheckouts/$',views.purchase_checkouts_view,name='purchase_checkouts_view'),
re_path(r'^planrequired/',views.planrequired_view,name='planrequired_view'), re_path(r'^planrequired/',views.planrequired_view,name='planrequired_view'),
re_path(r'^starttrial/$',views.start_trial_view,name='start_trial_view'), re_path(r'^starttrial/$',views.start_trial_view,name='start_trial_view'),
re_path(r'^startplantrial/$',views.start_plantrial_view,name='start_plantrial_view'), re_path(r'^startplantrial/$',views.start_plantrial_view,name='start_plantrial_view'),
@@ -744,6 +745,8 @@ urlpatterns = [
re_path(r'^plans/$', views.rower_select_instantplan, name='rower_select_instantplan'), re_path(r'^plans/$', views.rower_select_instantplan, name='rower_select_instantplan'),
re_path(r'^plans/(?P<id>[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})/$', re_path(r'^plans/(?P<id>[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})/$',
views.rower_view_instantplan, name='rower_view_instantplan'), views.rower_view_instantplan, name='rower_view_instantplan'),
re_path(r'^buyplan/(?P<id>\d+)/$',views.buy_trainingplan_view,name='buy_trainingplan_view'),
re_path(r'^confirmpurchaseplan/(?P<id>\d+)/$',views.confirm_trainingplan_purchase_view,name='confirm_trainingplan_purchase_view'),
re_path(r'^addinstantplan/$', views.add_instantplan_view, name='add_instantplan_view'), re_path(r'^addinstantplan/$', views.add_instantplan_view, name='add_instantplan_view'),
re_path(r'^deleteplan/(?P<pk>\d+)/$',login_required( re_path(r'^deleteplan/(?P<pk>\d+)/$',login_required(
views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'), views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'),

View File

@@ -84,6 +84,212 @@ def billing_view(request):
'planselectform':planselectform, 'planselectform':planselectform,
}) })
@login_required()
def buy_trainingplan_view(request,id=0):
if not PAYMENT_PROCESSING_ON:
url = reverse('promembership')
return HttpResponseRedirect(url)
r = request.user.rower
plan = get_object_or_404(InstantPlan,pk=id)
if r.paymentprocessor != 'braintree':
messages.error(request,"This purchase is currently only available through BrainTree (by PayPal)")
if id == 0 or id is None:
messages.error(request,"There was an error accessing this plan")
url = reverse('rower_view_instantplan',kwargs={
'id':plan.uuid,
})
return HttpResponseRedirect(url)
if request.method == 'POST':
billingaddressform = RowerBillingAddressForm(instance=r)
form = TrainingPlanForm(request.POST,user=request.user)
if billingaddressform.is_valid():
cd = billingaddressform.cleaned_data
for attr, value in cd.items():
setattr(r, attr, value)
r.save()
# redirect to payment confirmation view
if form.is_valid():
cd = form.cleaned_data
enddate = cd['enddate']
rowers = cd['rowers']
notes = cd['notes']
status = cd['status']
# get target and set enddate
try:
target = cd['target']
except KeyError:
try:
targetid = request.POST['target']
if targetid != '':
target = TrainingTarget.objects.get(id=int(targetid))
else:
target = None
except KeyError:
target = None
if target:
enddate = target.date
pars = {
'name':cd['name'],
'enddate':enddate,
'notes':notes,
'status':status,
'rower':rowers[0].id,
}
params = urllib.parse.urlencode(pars)
url = reverse('confirm_trainingplan_purchase_view',kwargs={'id':plan.id})
url = url + "?%s" % params
return HttpResponseRedirect(url)
else:
form = TrainingPlanForm(user=request.user)
billingaddressform = RowerBillingAddressForm(instance=r)
return render(request,
'buy_trainingplan.html',
{
'rower':r,
'plan':plan,
'billingaddressform':billingaddressform,
'form':form,
})
@login_required()
def purchase_checkouts_view(request):
if not PAYMENT_PROCESSING_ON:
url = reverse('promembership')
return HttpResponseRedirect(url)
r = request.user.rower
if request.method != 'POST':
url = reverse('rower_view_instantplan',kwargs={
'id':plan.uuid,
})
return HttpResponseRedirect(url)
form = TrainingPlanBillingForm(request.POST)
if form.is_valid():
data = form.cleaned_data
plan = InstantPlan.objects.get(id=data['plan'])
authorizationstring = 'Bearer '+settings.WORKOUTS_FIT_TOKEN
url = settings.WORKOUTS_FIT_URL+"/trainingplan/"+str(plan.uuid)
headers = {'Authorization':authorizationstring}
response = requests.get(url=url,headers=headers)
if response.status_code != 200:
messages.error(request,"Could not connect to the training plan server")
return HttpResponseRedirect(reverse('rower_select_instantplan'))
amount, success = braintreestuff.make_payment(r,data)
if success:
messages.info(request,"Your payment was completed and the sessions are copied to your calendar")
plansteps = response.json()
name = data['name']
enddate = data['enddate']
notes = data['notes']
status = data['status']
startdate = enddate-datetime.timedelta(days=plan.duration)
p = TrainingPlan(
name=name,
#target=target,
manager=r,
startdate=startdate,
enddate=enddate,status=status,
notes=notes,
)
p.save()
p.rowers.add(r)
create_sessions_from_json(plansteps,[r],startdate,r.user)
url = reverse('plannedsessions_view')
timeperiod = startdate.strftime('%Y-%m-%d')+'/'+enddate.strftime('%Y-%m-%d')
url = url+'?when='+timeperiod
return HttpResponseRedirect(url)
else:
messages.error(request,"There was a problem with your payment")
url = reverse('rower_view_instantplan',kwargs={
'id':plan.uuid,
})
return HttpResponseRedirect(url)
elif 'tac' not in request.POST:
try:
planid=int(request.POST['plan'])
enddate = request.POST['enddate']
rower = r.id
# incomplete
except IndexError:
messages.error(request,"There was an error in the payment form")
url = reverse("purchase_checkouts_view")
return HttpResponseRedirect(url)
url = reverse('rower_view_instantplan',kwargs={
'id':plan.uuid,
})
return HttpResponseRedirect(url)
@login_required()
def confirm_trainingplan_purchase_view(request,id = 0):
if not PAYMENT_PROCESSING_ON:
url = reverse('promembership')
return HttpResponseRedirect(url)
r = request.user.rower
plan = get_object_or_404(InstantPlan,pk=id)
if r.paymentprocessor != 'braintree':
messages.error(request,"This purchase is currently only available through BrainTree (by PayPal)")
if id == 0 or id is None:
messages.error(request,"There was an error accessing this plan")
url = reverse('rower_view_instantplan',kwargs={
'id':plan.uuid,
})
return HttpResponseRedirect(url)
client_token = braintreestuff.get_client_token(r)
enddate = request.GET.get('enddate',None)
name = request.GET.get('name','')
status = request.GET.get('status',True)
notes = request.GET.get('notes','')
if enddate is None:
messages.error(request,"There was an error accessing this plan")
url = reverse('rower_view_instantplan',kwargs={
'id':plan.uuid,
})
return render(request,
'confirm_trainingplan.html',
{
'plan':plan,
'client_token':client_token,
'rower':r,
'enddate':enddate,
'status':status,
'name':name,
'notes':notes,
})
@login_required() @login_required()
def upgrade_view(request): def upgrade_view(request):
if not PAYMENT_PROCESSING_ON: if not PAYMENT_PROCESSING_ON:

View File

@@ -2473,9 +2473,6 @@ class PlannedSessionDelete(DeleteView):
return obj return obj
@user_passes_test(can_plan,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
def rower_view_instantplan(request,id='',userid=0): def rower_view_instantplan(request,id='',userid=0):
r = getrequestrower(request,userid=userid) r = getrequestrower(request,userid=userid)
if not id: if not id:
@@ -2521,6 +2518,12 @@ def rower_view_instantplan(request,id='',userid=0):
).order_by("-date") ).order_by("-date")
if request.method == 'POST': if request.method == 'POST':
if not can_plan(request.user):
messages.error(request,'You must be on a <a href="/rowers/paidplans">paid plan</a> to use this functionality')
url = reverse('rower_view_instantplan',kwargs={
'id':id,
})
return HttpResponseRedirect(url)
form = TrainingPlanForm(request.POST,user=request.user) form = TrainingPlanForm(request.POST,user=request.user)
if form.is_valid(): if form.is_valid():

View File

@@ -77,6 +77,7 @@ from rowers.forms import (
VideoAnalysisCreateForm,WorkoutSingleSelectForm, VideoAnalysisCreateForm,WorkoutSingleSelectForm,
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm, VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
StravaChartForm,FitnessFitForm,PerformanceManagerForm, StravaChartForm,FitnessFitForm,PerformanceManagerForm,
TrainingPlanBillingForm,
) )
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy