590 lines
19 KiB
Python
590 lines
19 KiB
Python
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
import braintree
|
|
from django.utils import timezone
|
|
import datetime
|
|
from django.conf import settings
|
|
import sys
|
|
import django_rq
|
|
queue = django_rq.get_queue('default')
|
|
queuelow = django_rq.get_queue('low')
|
|
queuehigh = django_rq.get_queue('low')
|
|
|
|
import time
|
|
|
|
from braintree.exceptions.invalid_signature_error import InvalidSignatureError
|
|
import rowers.fakturoid as fakturoid
|
|
|
|
from rowers.utils import myqueue
|
|
from rowers.tasks import (
|
|
handle_send_email_transaction,
|
|
handle_send_email_subscription_update,
|
|
handle_send_email_subscription_create,
|
|
handle_send_email_failed_cancel,
|
|
#handle_send_email_transaction_notification,
|
|
)
|
|
|
|
from rowers import credits
|
|
from rowers.utils import dologging
|
|
|
|
import pandas as pd
|
|
|
|
from rowsandall_app.settings import (
|
|
BRAINTREE_MERCHANT_ID,BRAINTREE_PUBLIC_KEY,BRAINTREE_PRIVATE_KEY,
|
|
BRAINTREE_SANDBOX_MERCHANT_ID,BRAINTREE_SANDBOX_PUBLIC_KEY,
|
|
BRAINTREE_SANDBOX_PRIVATE_KEY, BRAINTREE_MERCHANT_ACCOUNT_ID
|
|
)
|
|
|
|
if settings.DEBUG or 'dev' in settings.SITE_URL: # pragma: no cover
|
|
gateway = braintree.BraintreeGateway(
|
|
braintree.Configuration(
|
|
braintree.Environment.Sandbox,
|
|
merchant_id=BRAINTREE_SANDBOX_MERCHANT_ID,
|
|
public_key=BRAINTREE_SANDBOX_PUBLIC_KEY,
|
|
private_key=BRAINTREE_SANDBOX_PRIVATE_KEY,
|
|
)
|
|
)
|
|
else:
|
|
gateway = braintree.BraintreeGateway(
|
|
braintree.Configuration(
|
|
braintree.Environment.Production,
|
|
merchant_id=BRAINTREE_MERCHANT_ID,
|
|
public_key=BRAINTREE_PUBLIC_KEY,
|
|
private_key=BRAINTREE_PRIVATE_KEY,
|
|
)
|
|
)
|
|
|
|
|
|
from rowers.models import Rower,PaidPlan, CoachingGroup
|
|
from rowers.utils import ProcessorCustomerError
|
|
|
|
def process_webhook(notification):
|
|
if not settings.TESTING: # pragma: no cover
|
|
with open('braintreewebhooks.log','a') as f:
|
|
t = time.localtime()
|
|
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
|
|
try:
|
|
f.write(timestamp+' '+notification.kind+'\n')
|
|
except TypeError:
|
|
f.write(timestamp+'\n')
|
|
if notification.kind == 'subscription_charged_successfully':
|
|
subscription = notification.subscription
|
|
rs = Rower.objects.filter(subscription_id=subscription.id)
|
|
if rs.count() == 0:
|
|
dologging('braintreewebhooks.log','Could not find rowers with subscription ID {id}'.format(
|
|
id=subscription.id
|
|
))
|
|
else:
|
|
r = rs[0]
|
|
transactions = subscription.transactions
|
|
if transactions:
|
|
amount = int(transactions[0].amount)
|
|
eurocredits = credits.upgrade(amount,r)
|
|
eurocredits = credits.upgrade(amount,r)
|
|
return send_invoice(notification.subscription)
|
|
if notification.kind == 'subscription_canceled':
|
|
subscription = notification.subscription
|
|
rs = Rower.objects.filter(subscription_id=subscription.id)
|
|
if rs.count() == 0: # pragma: no cover
|
|
with open('braintreewebhooks.log','a') as f:
|
|
f.write('Could not find rowers with subscription ID '+subscription.id+'\n')
|
|
return 0
|
|
r = rs[0]
|
|
result,mesg,errormsg = cancel_subscription(r,subscription.id)
|
|
if result:
|
|
with open('braintreewebhooks.log','a') as f:
|
|
f.write('Subscription canceled: '+str(subscription.id)+'\n')
|
|
return subscription.id
|
|
with open('braintreewebhooks.log','a') as f: # pragma: no cover
|
|
f.write('Could not cancel Subscription: '+str(subscription.id)+'\n')
|
|
return 0 # pragma: no cover
|
|
return 0
|
|
|
|
def send_invoice(subscription):
|
|
with open('braintreewebhooks.log','a') as f:
|
|
t = time.localtime()
|
|
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
|
|
f.write('Subscription ID '+str(subscription.id)+'\n')
|
|
subscription_id = subscription.id
|
|
rs = Rower.objects.filter(subscription_id=subscription_id)
|
|
if rs.count() == 0: # pragma: no cover
|
|
return 0
|
|
else:
|
|
r = rs[0]
|
|
with open('braintreewebhooks.log','a') as f:
|
|
f.write('Rower '+str(r)+'\n')
|
|
fakturoid_contact_id = fakturoid.get_contacts(r)
|
|
with open('braintreewebhooks.log','a') as f:
|
|
f.write('Fakturoid Contact ID '+str(fakturoid_contact_id)+'\n')
|
|
if not fakturoid_contact_id: # pragma: no cover
|
|
fakturoid_contact_id = fakturoid.create_contact(r)
|
|
with open('braintreewebhooks.log','a') as f:
|
|
f.write('Created Fakturoid Contact ID '+str(fakturoid_contact_id)+'\n')
|
|
transactions = subscription.transactions
|
|
if transactions:
|
|
amount = transactions[0].amount
|
|
with open('braintreewebhooks.log','a') as f:
|
|
f.write('Transaction amount '+str(amount)+'\n')
|
|
id = fakturoid.create_invoice(r,amount,subscription_id,dosend=True,
|
|
contact_id=fakturoid_contact_id)
|
|
return id
|
|
|
|
return 0 # pragma: no cover
|
|
|
|
|
|
def webhook(request):
|
|
try:
|
|
webhook_notification = gateway.webhook_notification.parse(
|
|
str(request.POST['bt_signature']),
|
|
request.POST['bt_payload'])
|
|
except InvalidSignatureError: # pragma: no cover
|
|
return 4
|
|
|
|
result = process_webhook(webhook_notification)
|
|
|
|
|
|
return result
|
|
|
|
def create_customer(rower,force=False):
|
|
if not rower.customer_id or force:
|
|
result = gateway.customer.create(
|
|
{
|
|
'first_name':rower.user.first_name,
|
|
'last_name':rower.user.last_name,
|
|
'email':rower.user.email,
|
|
})
|
|
if not result.is_success: # pragma: no cover
|
|
raise ProcessorCustomerError
|
|
else:
|
|
rower.customer_id = result.customer.id
|
|
rower.paymentprocessor = 'braintree'
|
|
rower.save()
|
|
return rower.customer_id
|
|
else: # pragma: no cover
|
|
return rower.customer_id
|
|
|
|
|
|
|
|
def get_client_token(rower):
|
|
try:
|
|
client_token = gateway.client_token.generate({
|
|
"customer_id":rower.customer_id,
|
|
})
|
|
except ValueError: # pragma: no cover
|
|
customer_id = create_customer(rower,force=True)
|
|
|
|
client_token = gateway.client_token.generate({
|
|
"customer_id": customer_id,
|
|
})
|
|
|
|
return client_token
|
|
|
|
def get_plans_costs(): # pragma: no cover
|
|
plans = gateway.plan.all()
|
|
|
|
localplans = PaidPlan.object.filter(paymentprocessor='braintree')
|
|
|
|
for plan in localplans:
|
|
for btplan in btplans:
|
|
if int(btplan.id) == plan.external_id:
|
|
plan.price = float(x)
|
|
plan.save()
|
|
|
|
return plans
|
|
|
|
def make_payment(rower,data):
|
|
nonce_from_the_client = data['payment_method_nonce']
|
|
nonce = gateway.payment_method_nonce.find(nonce_from_the_client)
|
|
info = nonce.three_d_secure_info
|
|
if nonce.type.lower() == 'creditcard': # pragma: no cover
|
|
if info is None or not info.liability_shifted:
|
|
return False,0
|
|
|
|
amount = data['amount']
|
|
amount = '{amount}'.format(amount=amount)
|
|
|
|
if 'plan' in data:
|
|
theplan = data['plan']
|
|
additional_text = 'Rowsandall Purchase Plan nr {theplan}'.format(theplan=theplan)
|
|
else:
|
|
additional_text = 'Rowsandall Purchase'
|
|
|
|
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
|
|
name = '{f} {l}'.format(
|
|
f = rower.user.first_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=additional_text)
|
|
|
|
try:
|
|
job = myqueue(queuehigh,handle_send_email_transaction,
|
|
name, rower.user.email, amount)
|
|
job = myqueue(queuehigh,handle_send_email_transation_notification,
|
|
name.rower.user.email, amount, additional_text)
|
|
except: # pragma: no cover
|
|
pass
|
|
|
|
return amount,True
|
|
else: # pragma: no cover
|
|
return 0,False
|
|
|
|
def update_subscription(rower,data,method='up'):
|
|
planid = data['plan']
|
|
plan = PaidPlan.objects.get(id=planid)
|
|
nonce_from_the_client = data['payment_method_nonce']
|
|
nonce = gateway.payment_method_nonce.find(nonce_from_the_client)
|
|
info = nonce.three_d_secure_info
|
|
if nonce.type.lower() == 'creditcard': # pragma: no cover
|
|
if info is None or not info.liability_shifted:
|
|
return False,0
|
|
amount = data['amount']
|
|
amount = '{amount:.2f}'.format(amount=amount)
|
|
|
|
|
|
gatewaydata = {
|
|
"price": amount,
|
|
"plan_id": plan.external_id,
|
|
"payment_method_nonce": nonce_from_the_client,
|
|
"options": {
|
|
"prorate_charges":True,
|
|
},
|
|
}
|
|
|
|
|
|
if plan.paymenttype == 'single':
|
|
gatewaydata['number_of_billing_cycles'] = 1
|
|
else: # pragma: no cover
|
|
gatewaydata['never_expires'] = True
|
|
|
|
try:
|
|
result = gateway.subscription.update(
|
|
rower.subscription_id,
|
|
gatewaydata
|
|
)
|
|
except: # pragma: no cover
|
|
return False,0
|
|
|
|
if result.is_success:
|
|
yesterday = (timezone.now()-datetime.timedelta(days=1)).date()
|
|
rower.paidplan = plan
|
|
eurocredits = credits.upgrade(int(amount),rower)
|
|
rower.planexpires = result.subscription.billing_period_end_date
|
|
rower.teamplanexpires = result.subscription.billing_period_end_date
|
|
rower.clubsize = plan.clubsize
|
|
rower.paymenttype = plan.paymenttype
|
|
rower.rowerplan = plan.shortname
|
|
rower.subscription_id = result.subscription.id
|
|
rower.protrialexpires = yesterday
|
|
rower.plantrialexpires = yesterday
|
|
rower.save()
|
|
name = '{f} {l}'.format(
|
|
f = rower.user.first_name,
|
|
l = rower.user.last_name,
|
|
)
|
|
|
|
if rower.paidplan != 'coach':
|
|
try:
|
|
coachgroup = rower.mycoachgroup
|
|
except CoachingGroup.DoesNotExist: # pragma: no cover
|
|
coachgroup = CoachingGroup()
|
|
coachgroup.save()
|
|
rower.mycoachgroup = coachgroup
|
|
rower.save()
|
|
|
|
athletes = Rower.objects.filter(coachinggroups__in=[rower.mycoachgroup]).distinct()
|
|
for athlete in athletes: # pragma: no cover
|
|
athlete.coachinggroups.remove(rower.mycoachgroup)
|
|
|
|
if method == 'up':
|
|
transactions = result.subscription.transactions
|
|
|
|
if transactions:
|
|
amount = transactions[0].amount
|
|
else: # pragma: no cover
|
|
amount = 0
|
|
else: # pragma: no cover
|
|
amount = 0
|
|
|
|
|
|
job = myqueue(queuehigh,
|
|
handle_send_email_subscription_update,
|
|
name, rower.user.email,
|
|
plan.name,
|
|
plan.paymenttype,
|
|
plan.price,
|
|
amount,
|
|
result.subscription.billing_period_end_date.strftime('%Y-%m-%d'),
|
|
method)
|
|
|
|
return True,amount
|
|
else: # pragma: no cover
|
|
errors = result.errors.for_object("subscription")
|
|
codes = [str(e.code) for e in errors]
|
|
create_new = False
|
|
proceed_codes = ['81901','81910']
|
|
for c in codes:
|
|
if c in proceed_codes:
|
|
create_new = True
|
|
|
|
if create_new:
|
|
return create_subscription(rower,data)
|
|
|
|
return False,0
|
|
|
|
return False,0 # pragma: no cover
|
|
|
|
|
|
def create_subscription(rower,data):
|
|
nonce_from_the_client = data['payment_method_nonce']
|
|
nonce = gateway.payment_method_nonce.find(nonce_from_the_client)
|
|
info = nonce.three_d_secure_info
|
|
paymenttype = nonce.type
|
|
|
|
if nonce.type != 'PayPalAccount': # pragma: no cover
|
|
if info is None or not info.liability_shifted:
|
|
return False,0
|
|
amount = data['amount']
|
|
|
|
planid = data['plan']
|
|
plan = PaidPlan.objects.get(id=planid)
|
|
|
|
|
|
# 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: # pragma: no cover
|
|
return False,0
|
|
|
|
result = gateway.subscription.create({
|
|
"payment_method_token": payment_method_token,
|
|
"plan_id": plan.external_id,
|
|
# "merchant_account_id": BRAINTREE_MERCHANT_ACCOUNT_ID,
|
|
})
|
|
|
|
if result.is_success:
|
|
yesterday = (timezone.now()-datetime.timedelta(days=1)).date()
|
|
rower.paidplan = plan
|
|
rower.planexpires = result.subscription.billing_period_end_date
|
|
rower.teamplanexpires = result.subscription.billing_period_end_date
|
|
rower.clubsize = plan.clubsize
|
|
rower.paymenttype = plan.paymenttype
|
|
rower.rowerplan = plan.shortname
|
|
rower.subscription_id = result.subscription.id
|
|
rower.protrialexpires = yesterday
|
|
rower.plantrialexpires = yesterday
|
|
|
|
rower.save()
|
|
name = '{f} {l}'.format(
|
|
f = rower.user.first_name,
|
|
l = rower.user.last_name,
|
|
)
|
|
|
|
|
|
recurring = plan.paymenttype
|
|
|
|
job = myqueue(
|
|
queuehigh,
|
|
handle_send_email_subscription_create,
|
|
name, rower.user.email,
|
|
plan.name,
|
|
recurring,
|
|
plan.price,
|
|
plan.price,
|
|
result.subscription.billing_period_end_date.strftime('%Y-%m-%d')
|
|
)
|
|
return True,plan.price
|
|
else: # pragma: no cover
|
|
return False,0
|
|
|
|
|
|
return False,0 # pragma: no cover
|
|
|
|
def cancel_subscription(rower,id):
|
|
themessages = []
|
|
errormessages = []
|
|
try:
|
|
result = gateway.subscription.cancel(id)
|
|
themessages.append("Subscription canceled")
|
|
except: # pragma: no cover
|
|
errormessages.append("We could not find the subscription record in our customer database. We have notified the site owner, who will contact you.")
|
|
|
|
|
|
name = '{f} {l}'.format(f = rower.user.first_name, l = rower.user.last_name)
|
|
|
|
|
|
job = myqueue(queuehigh,
|
|
handle_send_email_failed_cancel,
|
|
name, rower.user.email,rower.user.username,id)
|
|
|
|
return False, themessages, errormessages
|
|
|
|
basicplans = PaidPlan.objects.filter(price=0,paymentprocessor='braintree')
|
|
rower.paidplan = basicplans[0]
|
|
rower.teamplanexpires = timezone.now()
|
|
rower.planexpires = timezone.now()
|
|
rower.clubsize = 0
|
|
rower.rowerplan = 'basic'
|
|
rower.subscription_id = None
|
|
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: # pragma: no cover
|
|
raise ProcessorCustomerError("We could not find the customer in the database")
|
|
|
|
active_subscriptions = []
|
|
|
|
cards = result.credit_cards
|
|
for card in cards: # pragma: no cover
|
|
for subscription in card.subscriptions:
|
|
if subscription.status == 'Active':
|
|
active_subscriptions.append(subscription)
|
|
|
|
try:
|
|
paypal_accounts = result.paypal_accounts
|
|
for account in paypal_accounts: # pragma: no cover
|
|
for subscription in account.subscriptions:
|
|
if subscription.status == 'Active':
|
|
active_subscriptions.append(subscription)
|
|
except AttributeError: # pragma: no cover
|
|
pass
|
|
|
|
result = []
|
|
|
|
for subscription in active_subscriptions: # pragma: no cover
|
|
|
|
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,
|
|
'never_expires': subscription.never_expires
|
|
}
|
|
|
|
result.append(thedict)
|
|
|
|
return result
|
|
|
|
def get_transactions(start_date,end_date): # pragma: no cover
|
|
results = gateway.transaction.search(
|
|
braintree.TransactionSearch.created_at.between(
|
|
start_date,
|
|
end_date,
|
|
)
|
|
)
|
|
|
|
amounts = []
|
|
countries = []
|
|
addresses = []
|
|
card_countries = []
|
|
names = []
|
|
emails = []
|
|
dates = []
|
|
currencies = []
|
|
statuses = []
|
|
ids = []
|
|
usernames = []
|
|
customerids = []
|
|
transactionids = []
|
|
subscriptionids = []
|
|
ownids = []
|
|
|
|
countlines = [1 for transaction in results]
|
|
|
|
for transaction in results:
|
|
r = None
|
|
rs = Rower.objects.filter(
|
|
customer_id=transaction.customer['id'],
|
|
paymentprocessor='braintree')
|
|
if rs:
|
|
r = rs[0]
|
|
countries.append(r.country)
|
|
addresses.append('{street}, {city}, {postal_code}'.format(
|
|
street = r.street_address,
|
|
city = r.city,
|
|
postal_code = r.postal_code))
|
|
ownids.append(r.id)
|
|
usernames.append(r.user.username)
|
|
|
|
else:
|
|
countries.append(
|
|
transaction.credit_card_details.country_of_issuance)
|
|
ownids.append('unknown')
|
|
usernames.append('unknown')
|
|
addresses.append('')
|
|
|
|
|
|
emails.append(transaction.customer_details.email)
|
|
names.append('{f} {l}'.format(
|
|
f = transaction.customer['first_name'],
|
|
l = transaction.customer['last_name']
|
|
)
|
|
)
|
|
customerids.append(transaction.customer['id'])
|
|
transactionids.append(transaction.id)
|
|
subscriptionids.append(transaction.subscription_id)
|
|
amounts.append(transaction.amount)
|
|
dates.append(transaction.created_at)
|
|
currencies.append(transaction.currency_iso_code)
|
|
card_countries.append(
|
|
transaction.credit_card_details.country_of_issuance)
|
|
statuses.append(transaction.status)
|
|
|
|
|
|
df = pd.DataFrame({
|
|
'name':names,
|
|
'email':emails,
|
|
'date':dates,
|
|
'amount':amounts,
|
|
'currency':currencies,
|
|
'country':countries,
|
|
'card_country':card_countries,
|
|
'status':statuses,
|
|
'username':usernames,
|
|
'user_id':ownids,
|
|
'customer_id':customerids,
|
|
'transaction_id':transactionids,
|
|
'subscription_id':subscriptionids,
|
|
'address':addresses
|
|
}
|
|
)
|
|
|
|
return df
|
|
|
|
|
|
def mocktest(rower): # pragma: no cover
|
|
return '5'
|