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 = 10 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, ) 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 = [] # 1. Fetch subscription first so we can read paid_through_date try: subscription = gateway.subscription.find(id) paid_through = subscription.paid_through_date # may be None except Exception: 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"{rower.user.first_name} {rower.user.last_name}" _ = myqueue( queuehigh, handle_send_email_failed_cancel, name, rower.user.email, rower.user.username, id, ) return False, themessages, errormessages # 2. Attempt cancellation try: _ = gateway.subscription.cancel(id) themessages.append("Subscription canceled") except Exception: # 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"{rower.user.first_name} {rower.user.last_name}" _ = myqueue( queuehigh, handle_send_email_failed_cancel, name, rower.user.email, rower.user.username, id, ) return False, themessages, errormessages # 3. Update rower object (using paid_through_date) basicplans = PaidPlan.objects.filter(price=0, paymentprocessor='braintree') rower.paidplan = basicplans[0] # teamplanexpires stays "now" (as you had it) rower.teamplanexpires = timezone.now() # planexpires becomes the user's real, fully paid end date # fallback = now() if Braintree somehow returns None rower.planexpires = paid_through or 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'