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
amount = data['amount']
amount = '{amount:.f2}'.format(amount=amount)
amount = '{amount}'.format(amount=amount)
result = gateway.transaction.sale({
"amount": amount,
@@ -200,13 +200,18 @@ def make_payment(rower,data):
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,
name, rower.user.email, amount)
return amount,''
return amount,True
else:
return 0,''
return 0,False
def update_subscription(rower,data,method='up'):
planid = data['plan']

View File

@@ -83,11 +83,14 @@ def create_contact(rower):
# this should be triggered by a Braintree webhook
def create_invoice(rower,amount,braintreeid,dosend=True,
contact_id=None):
contact_id=None,name=None):
if not contact_id:
contact_id = get_contacts(rower)
if not name:
name = 'Rowsandall Subscription'
with open('braintreewebhooks.log','a') as f:
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),
'status': 'paid',
'lines': [{
'name': 'Rowsandall Subscription',
'name': name,
'quantity': '1',
'unit_price': str(amount),
'vat_rate': 0,

View File

@@ -101,6 +101,19 @@ class BillingForm(forms.Form):
paymenttype = forms.CharField(max_length=255,required=True)
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
class LoginForm(forms.Form):

View File

@@ -1612,7 +1612,7 @@ class TrainingPlan(models.Model):
rowers = models.ManyToManyField(Rower,related_name='planathletes',
verbose_name='Athletes')
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')
target = models.ForeignKey(TrainingTarget,blank=True,null=True,on_delete=models.SET_NULL)
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>
{% endif %}
<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 class="grid_2">
<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
the date it needs to complete in time.
</p>
{% if plan.price == 0 %}
<form enctype="multipart/form-data" action="" method="post">
{% else %}
<form enctype="multipart/form-data" action="/rowers/buyplan/{{ plan.id }}/" method="post">
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
{% if plan.price == 0 %}
<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>
</li>
<li class="grid_4">

View File

@@ -22,6 +22,11 @@
<p>Goal: {{ plan.goal }}</p>
{% endif %}
<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>
{% 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'^checkouts/$',views.checkouts_view,name='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'^starttrial/$',views.start_trial_view,name='start_trial_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/(?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'),
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'^deleteplan/(?P<pk>\d+)/$',login_required(
views.TrainingPlanDelete.as_view()),name='trainingplan_delete_view'),

View File

@@ -84,6 +84,212 @@ def billing_view(request):
'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()
def upgrade_view(request):
if not PAYMENT_PROCESSING_ON:

View File

@@ -2473,9 +2473,6 @@ class PlannedSessionDelete(DeleteView):
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):
r = getrequestrower(request,userid=userid)
if not id:
@@ -2521,6 +2518,12 @@ def rower_view_instantplan(request,id='',userid=0):
).order_by("-date")
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)
if form.is_valid():

View File

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