From 7bd18c1babdc13ed48160ae0892d87455735276e Mon Sep 17 00:00:00 2001 From: yackob03 Date: Wed, 2 Oct 2013 00:48:03 -0400 Subject: [PATCH] Checkpointing stripe work. --- app.py | 3 ++ config.py | 19 +++++++-- data/database.py | 1 + endpoints/api.py | 51 ++++++++++++++++++++++ requirements-nover.txt | 3 +- requirements.txt | 5 +++ static/js/app.js | 1 + static/js/controllers.js | 72 +++++++++++++++++++++++++++++++- static/partials/user-admin.html | 21 ++++++++++ templates/index.html | 2 +- test.db | Bin 40960 -> 40960 bytes 11 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 static/partials/user-admin.html diff --git a/app.py b/app.py index f00ec578c..486717bdc 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,6 @@ import logging import os +import stripe from flask import Flask from flask.ext.principal import Principal @@ -29,3 +30,5 @@ login_manager.login_view = 'signin' mail = Mail() mail.init_app(app) + +stripe.api_key = app.config['STRIPE_SECRET_KEY'] diff --git a/config.py b/config.py index 042dea3e8..6b50fe912 100644 --- a/config.py +++ b/config.py @@ -54,7 +54,18 @@ class LocalStorage(object): LOCAL_STORAGE_DIR = '/tmp/registry' -class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB): +class StripeTestConfig(object): + STRIPE_SECRET_KEY = 'sk_test_PEbmJCYrLXPW0VRLSnWUiZ7Y' + STRIPE_PUBLISHABLE_KEY = 'pk_test_uEDHANKm9CHCvVa2DLcipGRh' + + +class StripeLiveConfig(object): + STRIPE_SECRET_KEY = 'sk_live_TRuTHYwTvmrLeU3ib7Z9hpqE' + STRIPE_PUBLISHABLE_KEY = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu' + + +class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB, + StripeTestConfig): REGISTRY_SERVER = 'localhost:5000' LOGGING_CONFIG = { 'level': logging.DEBUG, @@ -62,7 +73,8 @@ class DebugConfig(FlaskConfig, MailConfig, LocalStorage, SQLiteDB): } -class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL): +class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, + StripeLiveConfig): REGISTRY_SERVER = 'localhost:5000' LOGGING_CONFIG = { 'level': logging.DEBUG, @@ -70,7 +82,8 @@ class LocalHostedConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL): } -class ProductionConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL): +class ProductionConfig(FlaskConfig, MailConfig, S3Storage, RDSMySQL, + StripeLiveConfig): REGISTRY_SERVER = 'quay.io' LOGGING_CONFIG = { 'stream': sys.stderr, diff --git a/data/database.py b/data/database.py index 5ff37345c..dba84fa8d 100644 --- a/data/database.py +++ b/data/database.py @@ -22,6 +22,7 @@ class User(BaseModel): password_hash = CharField() email = CharField(unique=True, index=True) verified = BooleanField(default=False) + stripe_id = CharField(index=True, null=True) class Visibility(BaseModel): diff --git a/endpoints/api.py b/endpoints/api.py index c0876f2a9..6cc4295a5 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1,4 +1,5 @@ import logging +import stripe from flask import request, make_response, jsonify, abort from flask.ext.login import login_required, current_user @@ -317,3 +318,53 @@ def delete_permissions(namespace, repository, username): return make_response('Deleted', 204) abort(403) # Permission denied + + +def subscription_view(stripe_subscription): + return { + 'current_period_start': stripe_subscription.current_period_start, + 'current_period_end': stripe_subscription.current_period_end, + 'plan': stripe_subscription.plan.id, + } + + +@app.route('/api/user/plan', methods=['PUT']) +@api_login_required +def subscribe(): + # Amount in cents + amount = 500 + + request_data = request.get_json() + plan = request_data['plan'] + card = request_data['token'] + + user = current_user.db_user + + if not user.stripe_id: + # Create the customer and plan simultaneously + cus = stripe.Customer.create(email=user.email, plan=plan, card=card) + user.stripe_id = cus.id + user.save() + + resp = jsonify(subscription_view(cus.subscription)) + resp.status_code = 201 + return resp + + else: + # Change the plan + cus = stripe.Customer.retrieve(user.stripe_id) + cus.plan = plan + cus.save() + return jsonify(subscription_view(cus.subscription)) + + +@app.route('/api/user/plan', methods=['GET']) +@api_login_required +def get_subscription(): + user = current_user.db_user + + if user.stripe_id: + cus = stripe.Customer.retrieve(user.stripe_id) + return jsonify(subscription_view(cus.subscription)) + + abort(404) diff --git a/requirements-nover.txt b/requirements-nover.txt index 9a45a60cd..c2c205e79 100644 --- a/requirements-nover.txt +++ b/requirements-nover.txt @@ -6,4 +6,5 @@ Flask-Login Flask-Mail python-dateutil boto -pymysql \ No newline at end of file +pymysql +stripe \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c442ed8c9..45761457c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,9 +9,14 @@ Werkzeug==0.9.4 argparse==1.2.1 blinker==1.3 boto==2.13.3 +distribute==0.6.34 +ipdb==0.8 +ipython==1.1.0 itsdangerous==0.23 peewee==2.1.4 py-bcrypt==0.4 python-dateutil==2.1 +requests==2.0.0 six==1.4.1 +stripe==1.9.5 wsgiref==0.1.2 diff --git a/static/js/app.js b/static/js/app.js index aa9b8f0e3..00561b78c 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -72,6 +72,7 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment'], function($pro when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl}). when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl}). when('/repository/', {title: 'Repositories', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}). + when('/user', {title: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}). when('/', {title: 'Quay', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}). otherwise({redirectTo: '/'}); }]). diff --git a/static/js/controllers.js b/static/js/controllers.js index 23bc13f64..3c922ca28 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -175,7 +175,7 @@ function RepoCtrl($scope, Restangular, $routeParams, $rootScope) { $scope.listImages = function() { if ($scope.imageHistory) { return; } - var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/tag/' + $scope.currentTag.name + '/images'); + var imageFetch = Restangular.one('repository/' + namespace + '/' + name + '/tag/' + $scope.currentTag.name + '/images'); imageFetch.get().then(function(resp) { $scope.imageHistory = resp.images; }); @@ -365,4 +365,74 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) { $rootScope.title = 'Unknown Repository'; $scope.loading = false; }); +} + +function UserAdminCtrl($scope, Restangular) { + $scope.plans = [ + { + title: 'Micro', + price: 700, + privateRepos: 5, + stripeId: 'micro', + }, + { + title: 'Small', + price: 1200, + privateRepos: 10, + stripeId: 'small', + }, + { + title: 'Medium', + price: 2200, + privateRepos: 20, + stripeId: 'medium', + }, + ]; + + var planDict = {}; + var i; + for(i = 0; i < $scope.plans.length; i++) { + planDict[$scope.plans[i].stripeId] = $scope.plans[i]; + } + + var getSubscription = Restangular.one('user/plan'); + getSubscription.get().then(function(sub) { + // User has a subscription + $scope.subscription = sub; + }); + + $scope.subscribe = function(planId) { + var submitToken = function(token) { + $scope.$apply(function() { + var subscriptionDetails = { + token: token.id, + plan: planId, + }; + + console.log(subscriptionDetails); + + var createSubscriptionRequest = Restangular.one('user/plan'); + createSubscriptionRequest.customPUT(subscriptionDetails).then(function() { + // Success + console.log('successfully created subscription'); + }, function() { + // Failure + console.log('failed to created subscription'); + }); + }); + }; + + console.log('Got request for plan: ' + planId); + var planDetails = planDict[planId] + StripeCheckout.open({ + key: 'pk_test_uEDHANKm9CHCvVa2DLcipGRh', + address: false, // TODO change to true + amount: planDetails.price, + currency: 'usd', + name: 'Quay ' + planDetails.title + ' Subscription', + description: 'Up to ' + planDetails.privateRepos + ' private repositories', + panelLabel: 'Subscribe', + token: submitToken + }); + }; } \ No newline at end of file diff --git a/static/partials/user-admin.html b/static/partials/user-admin.html new file mode 100644 index 000000000..7a006d92b --- /dev/null +++ b/static/partials/user-admin.html @@ -0,0 +1,21 @@ +
+
+
+
+
+ {{ plan.title }} + + + Subscribed + +
+
+ {{ plan.privateRepos }} Private Repositories
+ + + +
+
+
+
+
\ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 819d3e4f2..a411538af 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,9 +10,9 @@ - + diff --git a/test.db b/test.db index 6041988e4597128ce412d425e6c9c520ab4819e0..25c547a62ae39b501470b799f00825baaff94198 100644 GIT binary patch delta 208 zcmZoTz|?SnX@az13IhX!1`xx5{6rmN))WTaN6H&hW-~LgZDL{LVAEk>Uc#)iu~C7U ztBi?*LAH_AmeX*u2Y-xEa%pkAQM$2{mq|c?n_EC|aOUPE{0RaAYz+MjK=hl@kx7rK zgXzX(5ryi>Dhiu9vzXb%MMW8#%_l!lI3=N@P+U@!S&$l^nW9jZSd^TRSfpWOYO1+; rmEvW_O)4xba_kIB3_!G$QHyZ}6C2Y%<_efgLGt*xHKNM_GC4Cu delta 171 zcmZoTz|?SnX@az15(5K+1`xx5+(aE?)+7eqN9r3>W-~LgZ(?ENV6$gnUczj@vC){B zGnt86