From e649e669e19d808216ea891f484f082f007f3a82 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 13 Nov 2013 17:47:45 -0500 Subject: [PATCH] Check in a basic invoice view for organizations --- endpoints/api.py | 32 ++++++++++++++++++++ static/css/quay.css | 48 ++++++++++++++++++++++++++++++ static/js/controllers.js | 21 ++++++++++++++ static/partials/org-admin.html | 53 ++++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+) diff --git a/endpoints/api.py b/endpoints/api.py index e4fe31765..8f93bd0d8 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1252,6 +1252,38 @@ def subscribe(user, plan, token, accepted_plans): return resp +@app.route('/api/organization//invoices', methods=['GET']) +@api_login_required +def org_invoices_api(orgname): + def invoice_view(i): + return { + 'id': i.id, + 'date': i.date, + 'period_start': i.period_start, + 'period_end': i.period_end, + 'paid': i.paid, + 'amount_due': i.amount_due, + 'next_payment_attempt': i.next_payment_attempt, + 'attempted': i.attempted, + 'closed': i.closed, + 'total': i.total, + 'plan': i.lines.data[0].plan.id + } + + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + organization = model.get_organization(orgname) + if not organization.stripe_id: + abort(404) + + invoices = stripe.Invoice.all(customer=organization.stripe_id, count=12) + return jsonify({ + 'invoices': [invoice_view(i) for i in invoices.data] + }) + + abort(403) + + @app.route('/api/organization//plan', methods=['PUT']) @api_login_required def subscribe_org_api(orgname): diff --git a/static/css/quay.css b/static/css/quay.css index 59c1638d8..72d6f41d5 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -1638,6 +1638,54 @@ p.editable:hover i { display: inline-block; } +.org-admin .invoice-title { + padding: 6px; + cursor: pointer; +} + +.org-admin .invoice-status .success { + color: green; +} + +.org-admin .invoice-status .pending { + color: steelblue; +} + +.org-admin .invoice-status .danger { + color: red; +} + +.org-admin .invoice-amount:before { + content: '$'; +} + +.org-admin .invoice-details { + margin-left: 10px; + margin-bottom: 10px; + + padding: 4px; + padding-left: 6px; + border-left: 2px solid #eee !important; +} + +.org-admin .invoice-details td { + border: 0px solid transparent !important; +} + +.org-admin .invoice-details dl { + margin: 0px; +} + +.org-admin .invoice-details dd { + margin-left: 10px; + padding: 6px; + margin-bottom: 10px; +} + +.org-admin .invoice-title:hover { + color: steelblue; +} + .org-list h2 { margin-bottom: 20px; } diff --git a/static/js/controllers.js b/static/js/controllers.js index 456c1ebe6..e74c048ac 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -1187,6 +1187,10 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService // Load the list of plans. PlanService.getPlans(function(plans) { $scope.plans = plans.business; + $scope.plan_map = {}; + for (var i = 0; i < plans.business.length; ++i) { + $scope.plan_map[plans.business[i].stripeId] = plans.business[i]; + } }); var orgname = $routeParams.orgname; @@ -1194,6 +1198,23 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService $scope.orgname = orgname; $scope.membersLoading = true; $scope.membersFound = null; + $scope.invoiceLoading = true; + + $scope.loadInvoices = function() { + if ($scope.invoices) { return; } + $scope.invoiceLoading = true; + + var getInvoices = Restangular.one(getRestUrl('organization', orgname, 'invoices')); + getInvoices.get().then(function(resp) { + $scope.invoiceExpanded = {}; + $scope.invoices = resp.invoices; + $scope.invoiceLoading = false; + }); + }; + + $scope.toggleInvoice = function(id) { + $scope.invoiceExpanded[id] = !$scope.invoiceExpanded[id]; + }; $scope.loadMembers = function() { if ($scope.membersFound) { return; } diff --git a/static/partials/org-admin.html b/static/partials/org-admin.html index c9811e82a..bba531439 100644 --- a/static/partials/org-admin.html +++ b/static/partials/org-admin.html @@ -15,6 +15,7 @@ @@ -26,6 +27,58 @@
+ +
+
+ +
+ +
+ No invoices have been created +
+ +
+ + + + + + + + + + + + + + + + + + +
Billing Date/TimeAmount DueStatus
{{ invoice.date * 1000 | date:'medium' }}{{ invoice.amount_due / 100 }} + + Paid - Thank you! + Payment failed - Will retry soon + Payment pending + +
+
+
Billing Period
+
+ {{ invoice.period_start * 1000 | date:'mediumDate' }} - + {{ invoice.period_end * 1000 | date:'mediumDate' }} +
+
Plan
+
+ {{ plan_map[invoice.plan].title }} + {{ plan_map[invoice.plan].price / 100 }} +
+
+
+
+
+