From 530aeb4ccceb75e203e7aca7288be6af8bbd2cf3 Mon Sep 17 00:00:00 2001 From: Oliver Marks Date: Sat, 27 Feb 2016 16:19:03 +0000 Subject: [PATCH] more work on payments and login --- Dockerfile | 4 +- README.md | 4 +- site/authorize.py | 53 +++- site/config/settings.py | 20 +- site/data/badges.py | 57 ++++ site/data/site_user.py | 31 +++ site/data/sql/fetch_user_badges.sql | 3 + site/data/sql/get_user_bio.sql | 2 + site/data/sql/get_user_by_oauth_username.sql | 1 + site/libs/payments.py | 276 +++++++++++++++++++ site/pages/profile.py | 2 +- site/widgets/login_box.py | 2 +- 12 files changed, 426 insertions(+), 29 deletions(-) create mode 100644 site/data/badges.py create mode 100644 site/data/sql/fetch_user_badges.sql create mode 100644 site/data/sql/get_user_bio.sql create mode 100644 site/data/sql/get_user_by_oauth_username.sql create mode 100644 site/libs/payments.py diff --git a/Dockerfile b/Dockerfile index b1f6304..b525ffa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,13 +11,13 @@ RUN \ apt-get upgrade -y && \ apt-get install -y libssl-dev libffi-dev && \ apt-get install -y software-properties-common python-software-properties && \ - apt-get install -y python-pip python-dev python-requests python-lxml python-flask python-flask-login && \ + apt-get install -y python-MySQLdb python-psycopg2 python-pip python-dev python-requests python-lxml python-flask python-flask-login && \ apt-get install -y cssmin slimit && \ add-apt-repository -y ppa:oly/ppa && \ apt-get update && \ apt-get install -y python-scaffold -RUN pip install gocardless paypalrestsdk +RUN pip install gocardless paypalrestsdk pytz #allow access to flask EXPOSE 5000 5002 diff --git a/README.md b/README.md index df96bc3..5109ab6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ To suggest changes to the site hit the fork button on the github page, then make push your changes to your github account and create a pull request back into the main branch where it can be reviewed and merged if everything is okay. -The simplest way to setup this site locally to test and make changes is to run. + +The simplest way to setup this site is to use docker so please install that from this site https://docs.docker.com/engine/installation/ +and make sure the quick start guide works https://docs.docker.com/machine/get-started/ then you can use the commands below to test and make changes. docker build -t maidstone-hackspace . docker run -p 5000:5000 maidstone-hackspace diff --git a/site/authorize.py b/site/authorize.py index 6225226..25e9289 100644 --- a/site/authorize.py +++ b/site/authorize.py @@ -195,6 +195,8 @@ def oauth(provider, state=None): print '@@@@@@@' print request.url print oauth_provider.get('redirect_uri') + print oauth_provider.get('token_uri') + print oauth_provider.get('client_secret') # code error is todo with authorisation response oauth_session.fetch_token( oauth_provider.get('token_uri'), @@ -202,27 +204,50 @@ def oauth(provider, state=None): authorization_response=request.url, verify=oauth_verify) + #~ r = oauth_session.get('https://api.github.com/user') + #~ print r.content + # Fetch a protected resource, i.e. user profile - r = oauth_session.get(oauth_provider.get('user_uri')) + print oauth_provider.get('user_uri') + response = oauth_session.get(oauth_provider.get('user_uri')) + oauth_user = response.json() + + if provider is 'github': + oauth2_github_handle_user(oauth_user) + + if provider is 'facebook': + oauth2_github_handle_user(oauth_user) + + if provider is 'google': + oauth2_github_handle_user(oauth_user) - - oauth_user = r.json() + print oauth_user - user_details = site_user.get_by_email({ - 'email': oauth_user.get('email') + email = oauth_user.get('email') or '' + user_details = site_user.fetch_oauth_login({ + 'username': oauth_user.get('login') or '' }).get() - + + if oauth_user.get('login'): + #err what now we should probably error + pass + if not user_details: flash('Your new profile has been created, and your now logged in') + site_user.create_oauth_login().execute({ + 'username': oauth_user.get('login') or '', + 'provider': 'oauth'}) + site_user.create().execute({ - 'email': oauth_user.get('email'), + 'email': oauth_user.get('email') or '', 'password': 'oauth', 'profile_image': oauth_user.get('picture'), - 'username': oauth_user.get('email'), - 'first_name': oauth_user.get('given_name'), - 'last_name': oauth_user.get('family_name')}) - user_details = site_user.get_by_email({ + 'username': oauth_user.get('login'), + 'first_name': oauth_user.get('given_name') or '', + 'last_name': oauth_user.get('family_name') or ''}) + + user_details = site_user.get_by_ouath_login({ 'email': oauth_user.get('email') }).get() @@ -231,6 +256,10 @@ def oauth(provider, state=None): site_user.update_last_login().execute(user_details) return redirect('/profile') +def oauth2_github_handle_user(user): + print user + + @authorize_pages.route("/change-password/", methods=['GET']) @authorize_pages.route("/change-password", methods=['GET']) @@ -343,7 +372,7 @@ def login_screen(): header('Members Login') web.page.create('Member Login') web.page.section( - web.login_box.create().enable_oauth('google').render() + web.login_box.create().enable_oauth('google').enable_oauth('facebook').enable_oauth('github').render() ) #~ web.template.body.append(web.messages.render()) web.template.body.append(web.page.render()) diff --git a/site/config/settings.py b/site/config/settings.py index 63383b5..ea30969 100644 --- a/site/config/settings.py +++ b/site/config/settings.py @@ -23,19 +23,15 @@ database = { 'db': "maidstone_hackspace", 'port': 3306} - +# secret so not included in default settings oauth_live = False oauth_redirect_uri = app_domain + '/oauth' -oauth_conf = { - 'google': {}, - 'twitter': {} -} - - -google_calendar_id = 'contact@maidstone-hackspace.org.uk' -google_calendar_api_key = 'AIzaSyA98JvRDmplA9lVLZeKwrs1f2k17resLy0' - +oauth_conf = {} payment_providers = {} +google_calendar_id = '' +google_calendar_api_key = '' + + if os.path.exists('config/settings_dev.py'): print 'Using settings for dev enviroment' @@ -52,14 +48,14 @@ if os.path.exists('config/settings_live.py'): with web.template as setup: - #css + #css for jquery, material sprite sheet and custom css setup.persistent_header('') setup.persistent_header('') setup.persistent_header('') setup.persistent_header('') setup.persistent_header('') - #javascript + #javascript, using jquery and angular setup.persistent_header('') setup.persistent_header('') setup.persistent_header('') diff --git a/site/data/badges.py b/site/data/badges.py new file mode 100644 index 0000000..e2b0e32 --- /dev/null +++ b/site/data/badges.py @@ -0,0 +1,57 @@ +import os +import time +from collections import defaultdict +from scaffold.core.data.select import select_data +from scaffold.core.data.insert import insert_data +from scaffold.core.data.update import update_data +from scaffold.core.data.delete import delete_data +from scaffold.core.data.sql import query_builder + +query_builder.query_path = os.path.abspath('./data/sql/') + + +class create_badge(insert_data): + table = 'badges' + required = {'name'} + columns = {'name'} + +class assign_badge(insert_data): + table = 'user_badges' + required = {'user_id', 'badge_id'} + columns = {'user_id', 'badge_id'} + +class fetch_badges(select_data): + debug = True + table = 'badges' + required = {} + columns = {'id', 'name'} + +class fetch_badge(select_data): + debug = True + table = 'user_badges' + required = {'user_id'} + columns = {'user_id', 'badge_id'} + columns_where = {'user_id', 'badge_id'} + columns_optional_where = {'user_id', 'badge_id'} + columns_optional = {'user_id', 'badge_id'} + +class fetch_user_badges(select_data): + debug = True + table = 'user_badges' + columns = {'user_id', 'badge_id'} + #~ columns_where = {'user_id'} + columns_optional_where = {'user_id', 'badge_id'} + #~ columns_optional = {'user_id', 'badge_id'} + + + +def fetch_user_badges_grouped(): + badge_lookup = defaultdict(list) + for badge in fetch_user_badges(): + badge_lookup[badge.get('user_id')].append(badge.get('badge_id')) + return badge_lookup + +class remove_badge(delete_data): + table = 'user_badges' + required = {'id'} + columns = {'id'} diff --git a/site/data/site_user.py b/site/data/site_user.py index 71677d3..65c734f 100644 --- a/site/data/site_user.py +++ b/site/data/site_user.py @@ -113,6 +113,7 @@ class get_by_email(select_data): query_file = 'get_users.sql' columns_where = {'email'} + class get_by_username(select_data): required = {'email'} query_file = 'get_user_credentials.sql' @@ -122,3 +123,33 @@ class authorize(select_data): required = {'id'} query_file = 'get_user_credentials.sql' columns_where = {'id'} + + +class create_oauth_login(insert_data): + required = {'username', 'provider'} + query_file = 'get_user_by_oauth_username.sql' + columns_where = {'username', 'provider'} + + def calculated_data(self): + return {'registered': time.strftime('%Y-%m-%d %H:%M:%S')} + + def set(self, data): + data['registered'] = time.strftime('%Y-%m-%d %H:%M:%S') + super(create, self).set(data) + +class update_oauth_login(update_data): + required = {'username', 'provider'} + query_file = 'get_user_by_oauth_username.sql' + columns_where = {'username', 'provider'} + + def calculated_data(self): + return {'registered': time.strftime('%Y-%m-%d %H:%M:%S')} + + def set(self, data): + data['registered'] = time.strftime('%Y-%m-%d %H:%M:%S') + super(create, self).set(data) + +class fetch_oauth_login(select_data): + required = {'username', 'provider'} + query_file = 'get_user_by_oauth_username.sql' + columns_where = {'username', 'provider'} diff --git a/site/data/sql/fetch_user_badges.sql b/site/data/sql/fetch_user_badges.sql new file mode 100644 index 0000000..dd62c11 --- /dev/null +++ b/site/data/sql/fetch_user_badges.sql @@ -0,0 +1,3 @@ +select users.id as user_id, user_detail.id as user_detail_id, username, first_name, last_name, status, email, users.profile_image, last_login, description, skills + from users + join user_badges on users.id=user_badges.user_id diff --git a/site/data/sql/get_user_bio.sql b/site/data/sql/get_user_bio.sql new file mode 100644 index 0000000..1c12687 --- /dev/null +++ b/site/data/sql/get_user_bio.sql @@ -0,0 +1,2 @@ +select user_id, username, description, skills + from user_detail diff --git a/site/data/sql/get_user_by_oauth_username.sql b/site/data/sql/get_user_by_oauth_username.sql new file mode 100644 index 0000000..a821eee --- /dev/null +++ b/site/data/sql/get_user_by_oauth_username.sql @@ -0,0 +1 @@ +select user_id, last_login from user_oauth diff --git a/site/libs/payments.py b/site/libs/payments.py new file mode 100644 index 0000000..89451b8 --- /dev/null +++ b/site/libs/payments.py @@ -0,0 +1,276 @@ +from pprint import pprint +from config import settings +from datetime import datetime, timedelta +import pytz +import gocardless +import paypalrestsdk as paypal + +from config.settings import app_domain + +PROVIDER_ID = {'gocardless':1, 'paypal': 2} +PROVIDER_NAME = {1: 'gocardless', 2: 'paypal'} + +class payment: + """ + paypal reference = https://github.com/paypal/PayPal-Python-SDK + gocardless reference = https://github.com/paypal/PayPal-Python-SDK + """ + #~ def __call__(self, **args): + #~ return self + + def __init__(self, provider='gocardless', style='payment', mode='sandbox'): + self.provider = provider + self.environment = int(mode=='production') + self.provider_id = PROVIDER_ID.get(provider) + + if provider == 'paypal': + print settings.payment_providers[provider]['credentials'] + paypal.configure(**settings.payment_providers[provider]['credentials']) + return + + #~ environment = int('production' = settings.payment_providers[provider]['environment']) + gocardless.environment = settings.payment_providers[provider]['environment'] + gocardless.set_details(**settings.payment_providers[provider]['credentials']) + merchant = gocardless.client.merchant() + + def lookup_provider_by_id(self, provider_id): + return PROVIDER_NAME.get(provider_id, None) + + def make_donation(self, amount, reference, redirect_success, redirect_failure): + if self.provider == 'paypal': + payment = paypal.Payment({ + "intent": "sale", + "payer": {"payment_method": "paypal"}, + "redirect_urls": { + "return_url": redirect_success, + "cancel_url": redirect_failure}, + + "transactions": [{ + "amount": { + "total": amount, + "currency": "GBP"}, + "description": reference}]}) + + payment_response = payment.create() + if payment_response: + for link in payment.links: + if link.method == "REDIRECT": + redirect_url = str(link.href) + return str(redirect_url) + else: + print("Error while creating payment:") + print(payment.error) + + if self.provider == 'gocardless': + return gocardless.client.new_bill_url( + amount, + name=reference, + redirect_uri=redirect_success) + + return 'Error something went wrong' + + def fetch_subscriptions(self): + if self.provider == 'gocardless': + merchant = gocardless.client.merchant() + for paying_member in merchant.subscriptions(): + user=paying_member.user() + yield { + 'email': user.email, + 'start_date': paying_member.created_at, + 'reference': paying_member.id, + 'amount': paying_member.amount} + + if self.provider == 'paypal': + #~ start_date, end_date = "2014-07-01", "2014-07-20" + billing_agreement = paypal.BillingAgreement.find('') + print billing_agreement + print dir(billing_agreement) + #~ print billing_agreement.search_transactions(start_date, end_date) + #~ transactions = billing_agreement.search_transactions(start_date, end_date) + payment_history = paypal.Payment.all({"count": 2}) + + # List Payments + print("List Payment:") + print payment_history + for payment in payment_history.payments: + print(" -> Payment[%s]" % (payment.id)) + #~ print paypal.BillingAgreement.all() + history = paypal.BillingPlan.all( + {"status": "CREATED", "page_size": 5, "page": 1, "total_required": "yes"}) + print(history) + + print("List BillingPlan:") + for plan in history.plans: + print dir(plan) + print plan.to_dict() + print(" -> BillingPlan[%s]" % (plan.id)) + + #~ merchant = gocardless.client.merchant() + #~ for paying_member in merchant.subscriptions(): + #~ user=paying_member.user() + #~ yield { + #~ 'email': user.email, + #~ 'start_date': paying_member.created_at, + #~ 'reference': paying_member.id, + #~ 'amount': paying_member.amount} + + def subscribe_confirm(self, args): + if self.provider == 'gocardless': + response = gocardless.client.confirm_resource(args) + subscription = gocardless.client.subscription(args.get('resource_id')) + return { + 'amount': subscription.amount, + 'start_date': subscription.created_at, + 'reference': subscription.id + } + + if self.provider == 'paypal': + payment_token = args.get('token', '') + billing_agreement_response = paypal.BillingAgreement.execute(payment_token) + amount = 0 + print billing_agreement_response + print billing_agreement_response.id + for row in billing_agreement_response.plan.payment_definitions: + amount = row.amount.value + + return { + 'amount': amount, + 'start_date': billing_agreement_response.start_date, + 'reference': billing_agreement_response.id + } + + return None + + def unsubscribe(self, reference): + if self.provider == 'gocardless': + print 'unsubscribe gocardless' + subscription = gocardless.client.subscription(reference) + print subscription.cancel() + + if self.provider == 'paypal': + # this may be wrong + # ManageRecurringPaymentsProfileStatus + print reference + billing_plan = paypal.BillingAgreement.find(reference) + print billing_plan + print(billing_plan.error) + #~ billing_plan.replace([{"op": "replace","path": "/","value": {"state":"DELETED"}}]) + print(billing_plan.error) + #~ invoice = paypal.Invoice.find(reference) + options = { + "subject": "Cancelling membership", + "note": "Canceling invoice", + "send_to_merchant": True, + "send_to_payer": True + } + + if billing_plan.cancel(options): # return True or False + print("Invoice[%s] cancel successfully" % (invoice.id)) + else: + print(billing_plan.error) + + + def subscribe(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'): + print '%s/profile/gocardless' % app_domain + if self.provider == 'gocardless': + return gocardless.client.new_subscription_url( + amount=amount, + interval_length=interval_length, + interval_unit=interval_unit, + name=name, + redirect_uri=redirect_success) + + if self.provider == 'paypal': + billing_plan = paypal.BillingPlan({ + "name": name, + "description": "Membership subscription", + "merchant_preferences": { + "auto_bill_amount": "yes", + "cancel_url": redirect_failure, + "initial_fail_amount_action": "continue", + "max_fail_attempts": "1", + "return_url": redirect_success, + "setup_fee": { + "currency": "GBP", + "value": amount + } + }, + "payment_definitions": [{ + "amount": { + "currency": "GBP", + "value": amount + }, + "cycles": "0", + "frequency": interval_unit, + "frequency_interval": interval_length, + "name": "Regular 1", + "type": "REGULAR" + } + ], + "type": "INFINITE" + }) + print 'create bill' + + response = billing_plan.create() + + billing_plan = paypal.BillingPlan.find(billing_plan.id) + + if billing_plan.activate(): + start_date = datetime.utcnow() + timedelta(minutes=10) + billing_agreement = paypal.BillingAgreement({ + "name": billing_plan.name, + "description": name, + "start_date": start_date.strftime('%Y-%m-%dT%H:%M:%SZ'), + "plan": {"id": str(billing_plan.id)}, + "payer": {"payment_method": "paypal"} + }) + + if billing_agreement.create(): + print 'billing agreement id' + print billing_agreement.id + + for link in billing_agreement.links: + if link.rel == "approval_url": + approval_url = link.href + return approval_url + else: + print(billing_agreement.error) + print 'failed' + + def confirm(self, args): + confirm_details = {} + print '---------------------' + print args + from pprint import pprint + if self.provider == 'paypal': + print args.get('paymentId') + print args.get('PayerID') + payment = paypal.Payment.find(args.get('paymentId')) + pprint(payment) + print pprint(payment) + print payment + + confirm_details['name'] = payment['payer']['payer_info'].first_name + ' ' + payment['payer']['payer_info'].last_name + confirm_details['user'] = payment['payer']['payer_info'].email + confirm_details['status'] = payment.state + confirm_details['amount'] = payment['transactions'][0]['amount'].total + confirm_details['created'] = payment.create_time + confirm_details['reference'] = payment.id + pprint(confirm_details) + return confirm_details + + + if self.provider == 'gocardless': + bill_id = args.get('resource_id') + gocardless.client.confirm_resource(args) + if bill_id: + bill = gocardless.client.bill(bill_id) + confirm_details['name'] = bill.name + confirm_details['user'] = bill.user + confirm_details['status'] = bill.status + confirm_details['amount'] = bill.amount + #~ confirm_details['amount_minus_fees'] = bill.amount_minus_fees + confirm_details['created'] = bill.created_at + confirm_details['reference'] = bill_id + return confirm_details + return None diff --git a/site/pages/profile.py b/site/pages/profile.py index e3715d5..0e6eba9 100644 --- a/site/pages/profile.py +++ b/site/pages/profile.py @@ -11,7 +11,7 @@ from data.site_user import get_user_details, update_membership, update_membershi from data.profile import update_description, create_description, fetch_users from data import badges from data import members -from config.settings import gocardless_environment, gocardless_credentials +#~ from config.settings import gocardless_environment, gocardless_credentials from config.settings import app_domain from libs.payments import payment diff --git a/site/widgets/login_box.py b/site/widgets/login_box.py index 11623e4..7e30bf7 100644 --- a/site/widgets/login_box.py +++ b/site/widgets/login_box.py @@ -19,7 +19,7 @@ class control(base_widget): if 'google' in self.oauth_enabled: htm += '
' if 'facebook' in self.oauth_enabled: - htm += 'Facebook.
' + htm += 'Facebook.
' if 'github' in self.oauth_enabled: htm += 'GitHub
' htm += ''