Private
Public Access
1
0
Files
rowsandall/rowers/braintreestuff.py
2024-12-26 13:42:38 +01:00

586 lines
20 KiB
Python

from rowers.models import Rower, PaidPlan, CoachingGroup, iDokladToken
from rowers.utils import ProcessorCustomerError
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,
IDOKLAD_CLIENT_ID, IDOKLAD_CLIENT_SECRET, IDOKLAD_REDIRECT_URI,
)
import pandas as pd
from rowers.utils import dologging
from rowers import credits
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.utils import myqueue
import rowers.idoklad as idoklad
from braintree.exceptions.invalid_signature_error import InvalidSignatureError
from braintree.exceptions.not_found_error import NotFoundError
import time
import sys, traceback
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('high')
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,
)
)
def process_webhook(notification):
if not settings.TESTING: # pragma: no cover
dologging('braintreewebhooks.log','{kind}'.format(kind=notification.kind))
if notification.kind == 'subscription_charged_successfully':
subscription = notification.subscription
rs = Rower.objects.filter(subscription_id=subscription.id)
if rs.count() == 0: # pragma: no cover
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)
_ = 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
dologging('braintreewebhooks.log','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:
dologging('braintreewebhooks.log','Subscription canceled {id}'.format(id=subscription.id))
return subscription.id
dologging('braintreewebhooks.log','Could not cancel Subscription: ' +
str(subscription.id)+'\n')
return 0 # pragma: no cover
if notification.kind == 'subscription_charged_unsuccessfully':
dologging('braintreewebhooks.log','Subscription charged unsuccessfully')
subscription = notification.subscription
rs = Rower.objects.filter(subscription_id=subscription.id)
if rs.count() == 0: # pragma: no cover
dologging('braintreewebhooks.log', 'Could not find rowers with subscription ID {id}'.format(
id=subscription.id
))
else:
r = rs[0]
result, mesg, errormsg = cancel_subscription(r, subscription.id)
dologging('braintreewebhooks.log','Subscription canceled for rower {r} with subscription ID {id}'.format(
r = r.user.email,
id = subscription.id
))
return 0
def send_invoice(subscription):
dologging('braintreewebhooks.log', 'Subscription ID '+str(subscription.id))
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]
dologging('braintreewebhooks.log','Rower '+str(r)+'\n')
idoklad_contact_id = idoklad.get_contacts(r)
dologging('braintreewebhooks.log','Idoklad Contact ID '+str(idoklad_contact_id)+'\n')
if not idoklad_contact_id: # pragma: no cover
idoklad_contact_id = idoklad.create_contact(r)
dologging('braintreewebhooks.log','Created Idoklad Contact ID ' +
str(idoklad_contact_id)+'\n')
transactions = subscription.transactions
if transactions:
amount = transactions[0].amount
dologging('braintreewebhooks.log','Transaction amount '+str(amount)+'\n')
id = idoklad.create_invoice(r, amount, subscription_id, dosend=True,
contact_id=idoklad_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 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:
dologging('braintree.log','Nonce info {info}'.format(info=info))
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: # pragma: no cover
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,
)
idoklad_contact_id = idoklad.get_contacts(rower)
if not idoklad_contact_id:
idoklad_contact_id = idoklad.create_contact(rower)
_ = idoklad.create_invoice(rower, amount, transaction.id, dosend=True,
contact_id=idoklad_contact_id,
name=additional_text)
_ = myqueue(queuehigh, handle_send_email_transaction,
name, rower.user.email, amount)
return amount, True
else: # pragma: no cover
dologging('braintree.log','Payment failed {result}'.format(result=result))
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:
dologging('braintree.log','Nonce info {info}'.format(info=info))
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 NotFoundError:
rower.subscription_id = None
rower.save()
return create_subscription(rower, data)
except BaseException as e: # pragma: no cover
exc_type, exc_value, exc_traceback = sys.exc_info()
dologging('braintree.log','Payment failed with error')
dologging('braintree.log','{t}, {v}'.format(t=exc_type,v=exc_value))
trace_back = traceback.extract_tb(exc_traceback)
for trace in trace_back:
dologging('braintree.log','{t}'.format(t=trace))
return False, 0
if result.is_success:
yesterday = (timezone.now()-datetime.timedelta(days=1)).date()
rower.paidplan = plan
amount_int = int(float(amount))
_ = credits.upgrade(amount_int, 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
_ = 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)
_ = idoklad.create_invoice(rower, amount, result.subscription.id, dosend=True)
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','91920']
for c in codes:
if c in proceed_codes:
create_new = True
if create_new:
return create_subscription(rower, data)
dologging('braintree.log','Unrecognized proceed code {c}'.format(c=codes))
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:
dologging('braintree.log','Create Subscription. Nonce info {info}'.format(info=info))
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
dologging('braintree.log','Payment failed {result}'.format(result=result))
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
_ = 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:
_ = 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)
_ = 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 = []
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'