From 5ad95523250a0a9e377d53f6f614e21df3547e4d Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <ynerant@crans.org>
Date: Fri, 18 Jun 2021 23:54:17 +0200
Subject: [PATCH] Grant Constellation to use Note Kfet

---
 billing/admin.py                  | 37 +++++++++++++++++++--
 billing/models/__init__.py        |  4 +--
 billing/models/payment_methods.py | 20 ++++++++++++
 billing/urls.py                   |  2 ++
 billing/views/__init__.py         |  3 +-
 billing/views/note_kfet.py        | 54 +++++++++++++++++++++++++++++++
 constellation/settings.py         |  5 +++
 7 files changed, 120 insertions(+), 5 deletions(-)
 create mode 100644 billing/views/note_kfet.py

diff --git a/billing/admin.py b/billing/admin.py
index 8bc7e2c..3701a95 100644
--- a/billing/admin.py
+++ b/billing/admin.py
@@ -1,7 +1,9 @@
 from django import forms
+from django.conf import settings
 from django.contrib import admin
 
 from django.db import models
+from django.shortcuts import redirect
 from django.urls import reverse_lazy
 from django.utils.safestring import mark_safe
 from django.utils.text import capfirst
@@ -10,7 +12,8 @@ from django.utils.dateparse import parse_duration
 from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin
 
 from .models import ComnpayPaymentMethod, Entry, Invoice, \
-    MembershipProduct, MembershipPurchase, PaymentMethod, Product
+    MembershipProduct, MembershipPurchase, NoteKfetPaymentMethod, \
+    PaymentMethod, Product, StripePaymentMethod
 from .templatetags.pretty_money import pretty_money
 
 import datetime
@@ -181,7 +184,7 @@ class PaymentMethodAdmin(PolymorphicParentModelAdmin):
     """
     Manage different payment methods.
     """
-    child_models = [PaymentMethod, ComnpayPaymentMethod]
+    child_models = [PaymentMethod, ComnpayPaymentMethod, NoteKfetPaymentMethod, StripePaymentMethod]
     list_display = ('__str__', 'visible', 'payment_type',)
     list_filter = ('visible',)
 
@@ -201,6 +204,36 @@ class ComnpayPaymentMethodAdmin(PolymorphicChildModelAdmin):
     list_filter = ('visible',)
 
 
+@admin.register(StripePaymentMethod)
+class StripePaymentMethodAdmin(PolymorphicChildModelAdmin):
+    """
+    Manage Stripe payment methods.
+
+    In normal cases, there should be at most one object in this table.
+    """
+    list_display = ('__str__', 'visible',)
+    list_filter = ('visible',)
+
+
+@admin.register(NoteKfetPaymentMethod)
+class NoteKfetPaymentMethodAdmin(PolymorphicChildModelAdmin):
+    """
+    Manage Note Kfet payment methods.
+
+    In normal cases, there should be at most one object in this table.
+
+    This stores the current access and refresh tokens, which
+    should not be updated manually.
+    """
+    list_display = ('__str__', 'visible',)
+    list_filter = ('visible',)
+
+    def add_view(self, request, form_url='', extra_context=None):
+        return redirect(settings.NOTE_KFET_URL + "o/authorize/?client_id=" + settings.NOTE_KFET_CLIENT_ID
+                        + "&response_type=code&scope=" + settings.NOTE_KFET_SCOPES + "&state=toto"
+                        + f"&redirect_uri={request.scheme}://{request.get_host()}{reverse_lazy('billing:note_auth')}")
+
+
 @admin.register(Invoice)
 class InvoiceAdmin(admin.ModelAdmin):
     """
diff --git a/billing/models/__init__.py b/billing/models/__init__.py
index 25bc93e..1b24aee 100644
--- a/billing/models/__init__.py
+++ b/billing/models/__init__.py
@@ -1,9 +1,9 @@
 from .invoices import Entry, Invoice, Product
 from .memberships import MembershipProduct, MembershipPurchase
-from .payment_methods import ComnpayPaymentMethod, PaymentMethod, StripePaymentMethod
+from .payment_methods import ComnpayPaymentMethod, NoteKfetPaymentMethod, PaymentMethod, StripePaymentMethod
 
 __all__ = [
     'Entry', 'Invoice', 'Product',
-    'PaymentMethod', 'ComnpayPaymentMethod', 'StripePaymentMethod',
+    'PaymentMethod', 'ComnpayPaymentMethod', 'NoteKfetPaymentMethod', 'StripePaymentMethod',
     'MembershipProduct', 'MembershipPurchase',
 ]
diff --git a/billing/models/payment_methods.py b/billing/models/payment_methods.py
index a0f0f1e..c19ad4b 100644
--- a/billing/models/payment_methods.py
+++ b/billing/models/payment_methods.py
@@ -12,6 +12,7 @@ class PaymentMethod(PolymorphicModel):
     )
 
     visible = models.BooleanField(
+        default=False,
         verbose_name=_("visible"),
         help_text=_("if enabled, all members will see this payment method."),
     )
@@ -48,3 +49,22 @@ class StripePaymentMethod(PaymentMethod):
     class Meta:
         verbose_name = _("stripe payment method")
         verbose_name_plural = _("stripe payment methods")
+
+
+class NoteKfetPaymentMethod(PaymentMethod):
+    access_token = models.CharField(
+        max_length=64,
+        verbose_name=_("access token"),
+    )
+
+    refresh_token = models.CharField(
+        max_length=64,
+        verbose_name=_("refresh token"),
+    )
+
+    def get_payment_url(self, invoice):
+        return reverse_lazy('billing:note', args=(invoice.pk,))
+
+    class Meta:
+        verbose_name = _("Note Kfet payment method")
+        verbose_name_plural = _("Note Kfet payment methods")
diff --git a/billing/urls.py b/billing/urls.py
index 9b5a4b5..3315ef1 100644
--- a/billing/urls.py
+++ b/billing/urls.py
@@ -17,4 +17,6 @@ urlpatterns = [
     path('stripe/redirect/<int:pk>/', views.RedirectStripeView.as_view(), name='stripe_redirect'),
     path('stripe/success/', views.StripeSuccessView.as_view(), name='stripe_success'),
     path('stripe/fail/', views.StripeFailView.as_view(), name='stripe_fail'),
+
+    path('note/authorize/', views.NoteKfetAuthorizeView.as_view(), name='note_auth'),
 ]
diff --git a/billing/views/__init__.py b/billing/views/__init__.py
index 623dffe..9d6ee45 100644
--- a/billing/views/__init__.py
+++ b/billing/views/__init__.py
@@ -1,9 +1,10 @@
 from .comnpay import ComnpayFailView, ComnpaySuccessView, IPNComnpayView, RedirectComnpayView
+from .note_kfet import NoteKfetAuthorizeView
 from .purchase import InvoiceListView, PurchaseView, RenderInvoiceView
 from .stripe import StripeFailView, StripeSuccessView, RedirectStripeView
 
 __all__ = [
-    "ComnpayFailView", "ComnpaySuccessView", "InvoiceListView", "IPNComnpayView",
+    "ComnpayFailView", "ComnpaySuccessView", "InvoiceListView", "IPNComnpayView", "NoteKfetAuthorizeView",
     "PurchaseView", "RedirectComnpayView", "RedirectStripeView", "RenderInvoiceView",
     "StripeFailView", "StripeSuccessView",
 ]
diff --git a/billing/views/note_kfet.py b/billing/views/note_kfet.py
new file mode 100644
index 0000000..2620aa6
--- /dev/null
+++ b/billing/views/note_kfet.py
@@ -0,0 +1,54 @@
+import requests
+from django.conf import settings
+from django.contrib import messages
+from django.urls import reverse_lazy
+from django.utils.translation import gettext_lazy as _
+from django.views.generic import RedirectView
+
+from billing.models import NoteKfetPaymentMethod
+
+
+class NoteKfetAuthorizeView(RedirectView):
+    def get(self, request, *args, **kwargs):
+        if 'code' not in request.GET or 'state' not in request.GET:
+            messages.error(request, _("Bad request"))
+            return super().get(request, *args, **kwargs)
+
+        if request.GET['state'] != 'toto':  # FIXME use better state code
+            messages.error(request, _("Invalid state code"))
+            return super().get(request, *args, **kwargs)
+
+        code = request.GET['code']
+
+        response = requests.post(settings.NOTE_KFET_URL + "o/token/", data={
+            'client_id': settings.NOTE_KFET_CLIENT_ID,
+            'client_secret': settings.NOTE_KFET_CLIENT_SECRET,
+            'grant_type': 'authorization_code',
+            'code': code,
+        })
+        resp_json = response.json()
+
+        if 'error' not in resp_json:
+            resp_json = response.json()
+            access_token = resp_json['access_token']
+            refresh_token = resp_json['refresh_token']
+            payment_method, created = NoteKfetPaymentMethod.objects.get_or_create()
+            if created:
+                payment_method.name = "Note Kfet"
+            payment_method.access_token = access_token
+            payment_method.refresh_token = refresh_token
+            payment_method.save()
+
+            self.payment_method = payment_method
+
+            messages.success(request, _("Authorization succeed"))
+        else:
+            messages.error(request, _("Error:") + " " + str(resp_json))
+
+        return super().get(request, *args, **kwargs)
+
+    def get_redirect_url(self, *args, **kwargs):
+        if hasattr(self, 'payment_method'):
+            return reverse_lazy('admin:billing_paymentmethod_change', args=(self.payment_method.pk,))
+        else:
+            return reverse_lazy('admin:billing_paymentmethod_changelist')
diff --git a/constellation/settings.py b/constellation/settings.py
index 4e1105a..d49454c 100644
--- a/constellation/settings.py
+++ b/constellation/settings.py
@@ -247,6 +247,11 @@ COMNPAY_SECRET_KEY = 'CHANGEME'
 STRIPE_PUBLIC_KEY = "CHANGEME"
 STRIPE_PRIVATE_KEY = "CHANGEME"
 
+NOTE_KFET_URL = "https://note.crans.org/"
+NOTE_KFET_CLIENT_ID = "CHANGEME"
+NOTE_KFET_CLIENT_SECRET = "CHANGEME"
+NOTE_KFET_SCOPES = "6_1 20_23"  # View aliases, make a transaction between a user and Crans
+
 try:
     from .settings_local import *  # noqa: F401, F403&
 except ImportError:
-- 
GitLab