From b3539ef829691c65ed1cef15c7edb6cc34f8f0f9 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 7 Sep 2019 23:12:04 +0200 Subject: [PATCH] Improve subscr cancellation, add unusub feedback --- ccvpn/settings.py | 3 + locale/fr/LC_MESSAGES/django.po | 149 ++++++++++-------- payments/admin.py | 12 +- payments/backends/paypal.py | 34 +++- payments/backends/stripe.py | 1 + .../migrations/0006_auto_20190907_2029.py | 26 +++ payments/models.py | 17 +- payments/urls.py | 15 +- payments/views.py | 141 +++++++---------- static/css/style.css | 18 +++ templates/lambdainst/account.html | 22 +-- templates/payments/cancel_subscr.html | 32 ++++ 12 files changed, 281 insertions(+), 189 deletions(-) create mode 100644 payments/migrations/0006_auto_20190907_2029.py create mode 100644 templates/payments/cancel_subscr.html diff --git a/ccvpn/settings.py b/ccvpn/settings.py index 6c3f417..57447f1 100644 --- a/ccvpn/settings.py +++ b/ccvpn/settings.py @@ -232,6 +232,9 @@ PAYMENTS_BACKENDS = { # 'receiver': '', # PayPal primary address if different # 'currency': 'EUR', # 'header_image': '', + 'api_username': ', + 'api_password': '', + 'api_sig': '', }, 'stripe': { 'enabled': False, diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index a908baa..8cffafd 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-08-23 02:40+0000\n" +"POT-Creation-Date: 2019-09-07 21:02+0000\n" "PO-Revision-Date: 2016-04-07 01:32+0000\n" "Last-Translator: \n" "Language-Team: \n" @@ -264,19 +264,19 @@ msgstr "Config" msgid "Status" msgstr "État" -#: payments/admin.py:11 +#: payments/admin.py:12 msgid "Mark as cancelled (do not actually cancel)" msgstr "Marquer comme annulé (n'annule pas)" -#: payments/admin.py:36 payments/admin.py:91 +#: payments/admin.py:37 payments/admin.py:92 msgid "Payment Data" msgstr "Données de paiement" -#: payments/admin.py:59 +#: payments/admin.py:60 msgid "Amount" msgstr "Montant" -#: payments/admin.py:63 +#: payments/admin.py:64 msgid "Paid amount" msgstr "Montant payé" @@ -351,11 +351,11 @@ msgstr "" "En attente de la confirmation par Paypal... Cette étape peut durer quelques " "minutes..." -#: payments/backends/paypal.py:13 payments/backends/paypal.py:14 +#: payments/backends/paypal.py:14 payments/backends/paypal.py:15 msgid "PayPal" msgstr "PayPal" -#: payments/backends/paypal.py:51 +#: payments/backends/paypal.py:59 msgid "" "Waiting for PayPal to confirm the transaction... It can take up to a few " "minutes..." @@ -416,20 +416,17 @@ msgid "Every year" msgstr "Tous les ans" #: payments/views.py:156 -msgid "" -"Error subscribing. It usually means you don't have enough money available." -msgstr "" -"Il y a une erreur à l'inscription. C'est le plus souvent un manque de fonds " -"sur la carte." - -#: payments/views.py:159 -msgid "Subscribed!" -msgstr "Abonné!" - -#: payments/views.py:197 msgid "Subscription cancelled!" msgstr "Abonnement annulé!" +#: payments/views.py:158 +msgid "" +"Could not cancel the subscription. It may have already been cancelled or " +"caused an error." +msgstr "" +"Impossible d'annuler le paiement récurrent. Il a peut-être été déjà annulé " +"ou une erreur est survenue." + #: templates/account_layout.html:12 msgid "Config Download" msgstr "Configuration" @@ -443,7 +440,7 @@ msgstr "Paiements" msgid "Models in the %(name)s application" msgstr "" -#: templates/admin/index.html:31 templates/lambdainst/account.html:148 +#: templates/admin/index.html:31 templates/lambdainst/account.html:128 msgid "Add" msgstr "Ajouter" @@ -676,19 +673,11 @@ msgstr "" "Votre compte est actif. Votre abonnement sera automatiquement renouvelé le " "%(until)s via %(backend)s." -#: templates/lambdainst/account.html:28 -msgid "You can cancel it from PayPal account." -msgstr "Vous pouvez annuler depuis votre compte PayPal." - -#: templates/lambdainst/account.html:35 -msgid "Cancel Subscription" -msgstr "Annuler l'abonnement" +#: templates/lambdainst/account.html:26 +msgid "cancel" +msgstr "annuler" -#: templates/lambdainst/account.html:43 -msgid "Do you really want to cancel your subscription?" -msgstr "Voulez-vous vraimer annuler votre abonnement ?" - -#: templates/lambdainst/account.html:50 +#: templates/lambdainst/account.html:30 #, python-format msgid "" "Your subscription is waiting for confirmation by %(backend)s. It may take up " @@ -697,17 +686,17 @@ msgstr "" "Votre abonnement est en attente de confirmation par %(backend)s. Cette étape " "peut prendre jusqu'à quelques minutes..." -#: templates/lambdainst/account.html:58 +#: templates/lambdainst/account.html:38 #, python-format msgid "Your account is paid until %(until)s" msgstr "Votre compte est activé jusqu'au %(until)s" -#: templates/lambdainst/account.html:61 +#: templates/lambdainst/account.html:41 #, python-format msgid "(%(left)s left)" msgstr "(%(left)s restant)" -#: templates/lambdainst/account.html:67 +#: templates/lambdainst/account.html:47 msgid "" "You can activate your free trial account for two hours periods for up to one " "week, by clicking this button:" @@ -715,75 +704,75 @@ msgstr "" "Vous pouvez activez votre compte d'essai pour des périodes de deux heures " "pendant jusqu'à une semaine avec ce bouton:" -#: templates/lambdainst/account.html:76 +#: templates/lambdainst/account.html:56 msgid "Activate" msgstr "Activer" -#: templates/lambdainst/account.html:93 +#: templates/lambdainst/account.html:73 msgid "Your account is not paid." msgstr "Votre compte n'est pas payé." -#: templates/lambdainst/account.html:102 templates/payments/list.html:6 +#: templates/lambdainst/account.html:82 templates/payments/list.html:6 msgid "Subscription" msgstr "Abonnement" -#: templates/lambdainst/account.html:110 +#: templates/lambdainst/account.html:90 msgid "Pay every" msgstr "Payer tous les" -#: templates/lambdainst/account.html:112 templates/lambdainst/account.html:113 -#: templates/lambdainst/account.html:114 templates/lambdainst/account.html:151 -#: templates/lambdainst/account.html:152 templates/lambdainst/account.html:153 +#: templates/lambdainst/account.html:92 templates/lambdainst/account.html:93 +#: templates/lambdainst/account.html:94 templates/lambdainst/account.html:131 +#: templates/lambdainst/account.html:132 templates/lambdainst/account.html:133 msgid "months" msgstr "mois" -#: templates/lambdainst/account.html:119 templates/lambdainst/account.html:158 +#: templates/lambdainst/account.html:99 templates/lambdainst/account.html:138 msgid "by" msgstr "par" -#: templates/lambdainst/account.html:131 +#: templates/lambdainst/account.html:111 msgid "Subscribe" msgstr "S'abonner" -#: templates/lambdainst/account.html:133 +#: templates/lambdainst/account.html:113 msgid "You can cancel the recurring payment at any time." msgstr "Vous pouvez annuler le paiement récurrent à n'importe quel moment." -#: templates/lambdainst/account.html:140 +#: templates/lambdainst/account.html:120 msgid "One-time payment" msgstr "Paiement ponctuel" -#: templates/lambdainst/account.html:150 +#: templates/lambdainst/account.html:130 msgid "month" msgstr "mois" -#: templates/lambdainst/account.html:170 +#: templates/lambdainst/account.html:150 msgid "Buy Now" msgstr "Acheter" -#: templates/lambdainst/account.html:172 +#: templates/lambdainst/account.html:152 msgid "If you still have time, it will be added." msgstr "S'il vous reste du temps, il sera ajouté." -#: templates/lambdainst/account.html:183 +#: templates/lambdainst/account.html:163 msgid "Gift code" msgstr "Code cadeau" -#: templates/lambdainst/account.html:189 +#: templates/lambdainst/account.html:169 msgid "Redeem gift code" msgstr "Récupérer le code cadeau" -#: templates/lambdainst/account.html:199 +#: templates/lambdainst/account.html:179 msgid "" "Get two weeks for free for every referral that takes at least one month!" msgstr "" "Gagnez deux semaines gratuites pour chaque client qui a utilisé ce lien !" -#: templates/lambdainst/account.html:202 +#: templates/lambdainst/account.html:182 msgid "Share this link" msgstr "Partagez ce lien" -#: templates/lambdainst/account.html:206 +#: templates/lambdainst/account.html:186 msgid "tweet" msgstr "" @@ -895,7 +884,7 @@ msgstr "IP Partagée" msgid "Bandwidth" msgstr "Bande passante" -#: templates/lambdainst/logs.html:27 tickets/admin.py:50 tickets/models.py:58 +#: templates/lambdainst/logs.html:27 tickets/admin.py:50 tickets/models.py:57 msgid "Open" msgstr "Open" @@ -1000,7 +989,7 @@ msgid "Guides" msgstr "Guides" #: templates/layout.html:57 templates/tickets/index.html:6 -#: templates/tickets/layout.html:8 tickets/models.py:14 +#: templates/tickets/layout.html:8 tickets/models.py:13 msgid "Support" msgstr "Support" @@ -1032,6 +1021,23 @@ msgstr "C'est open-source!" msgid "Any question? Chat with us" msgstr "Une question ? Contactez nous" +#: templates/payments/cancel_subscr.html:8 +#: templates/payments/cancel_subscr.html:24 +msgid "Cancel Subscription" +msgstr "Annuler l'abonnement" + +#: templates/payments/cancel_subscr.html:15 +msgid "We're sorry to see you go." +msgstr "Nous sommes désolés que vous nous quittiez." + +#: templates/payments/cancel_subscr.html:16 +msgid "" +"Would you like to tell us why, or leave any feedback so we can improve our " +"service?" +msgstr "" +"Voudriez-vous nous dire pourquoi, ou laisser un retour pour nous permettre " +"d'améliorer notre service ?" + #: templates/payments/form.html:7 templates/payments/view.html:13 msgid "Payment" msgstr "Paiement" @@ -1188,7 +1194,7 @@ msgstr "Nouvelle réponse à votre ticket:" msgid "Open Ticket" msgstr "Créer le ticket" -#: templates/tickets/view.html:7 tickets/models.py:66 tickets/models.py:72 +#: templates/tickets/view.html:7 tickets/models.py:65 tickets/models.py:71 #: tickets/views.py:116 tickets/views.py:146 msgid "Ticket:" msgstr "Ticket:" @@ -1221,7 +1227,7 @@ msgstr "Fermer les tickets séléctionnés" msgid "Re-opened" msgstr "Ré-ouvert" -#: tickets/admin.py:52 tickets/models.py:53 +#: tickets/admin.py:52 tickets/models.py:52 msgid "Closed" msgstr "Fermé" @@ -1233,30 +1239,45 @@ msgstr "Catégorie" msgid "Message" msgstr "Message" -#: tickets/models.py:15 +#: tickets/models.py:14 msgid "Security" msgstr "Sécurité" -#: tickets/models.py:16 +#: tickets/models.py:15 msgid "Account / Billing" msgstr "Compte / Facturation" -#: tickets/models.py:36 +#: tickets/models.py:35 msgid "Can view any ticket" msgstr "Peut voir n'importe quel ticket" -#: tickets/models.py:37 +#: tickets/models.py:36 msgid "Can reply to any ticket" msgstr "Peut répondre à n'importe quel ticket" -#: tickets/models.py:38 +#: tickets/models.py:37 msgid "Can view private messages on tickets" msgstr "Peut voir les messages privés" -#: tickets/models.py:39 +#: tickets/models.py:38 msgid "Can post private messages on tickets" msgstr "Peut envoyer des messages privés" -#: tickets/models.py:56 +#: tickets/models.py:55 msgid "Waiting for staff" msgstr "En attente du support" + +#~ msgid "" +#~ "Error subscribing. It usually means you don't have enough money available." +#~ msgstr "" +#~ "Il y a une erreur à l'inscription. C'est le plus souvent un manque de " +#~ "fonds sur la carte." + +#~ msgid "Subscribed!" +#~ msgstr "Abonné!" + +#~ msgid "You can cancel it from PayPal account." +#~ msgstr "Vous pouvez annuler depuis votre compte PayPal." + +#~ msgid "Do you really want to cancel your subscription?" +#~ msgstr "Voulez-vous vraimer annuler votre abonnement ?" diff --git a/payments/admin.py b/payments/admin.py index 29cdecf..8b21633 100644 --- a/payments/admin.py +++ b/payments/admin.py @@ -3,7 +3,8 @@ from django.shortcuts import resolve_url from django.contrib import admin from django.utils.html import format_html from django.utils.translation import ugettext_lazy as _ -from .models import Payment, Subscription +from django.utils.text import Truncator +from .models import Payment, Subscription, Feedback def subscr_mark_as_cancelled(modeladmin, request, queryset): @@ -116,6 +117,15 @@ class SubscriptionAdmin(admin.ModelAdmin): return link(object.backend_extid, ext_url) backend_extid_link.allow_tags = True +class FeedbackAdmin(admin.ModelAdmin): + model = Feedback + list_display = ('user', 'created', 'short_message') + readonly_fields = ('user', 'created', 'message', 'subscription') + + def short_message(self, obj): + return Truncator(obj.message).chars(80) + admin.site.register(Payment, PaymentAdmin) admin.site.register(Subscription, SubscriptionAdmin) +admin.site.register(Feedback, FeedbackAdmin) diff --git a/payments/backends/paypal.py b/payments/backends/paypal.py index db5dafc..ecee585 100644 --- a/payments/backends/paypal.py +++ b/payments/backends/paypal.py @@ -4,6 +4,7 @@ from urllib.parse import urlencode from urllib.request import urlopen from django.urls import reverse from django.conf import settings as project_settings +import requests from .base import BackendBase @@ -22,13 +23,20 @@ class PaypalBackend(BackendBase): self.account_address = settings.get('address') self.receiver_address = settings.get('receiver', self.account_address) + self.api_username = settings.get('api_username') + self.api_password = settings.get('api_password') + self.api_sig = settings.get('api_sig') + if self.test: + default_nvp = 'https://api-3t.sandbox.paypal.com/nvp' default_api = 'https://www.sandbox.paypal.com/' else: + default_nvp = 'https://api-3t.paypal.com/nvp' default_api = 'https://www.paypal.com/' self.api_base = settings.get('api_base', default_api) + self.nvp_api_base = settings.get('nvp_api_base', default_nvp) - if self.account_address: + if self.account_address and self.api_username and self.api_password and self.api_sig: self.backend_enabled = True def new_payment(self, payment): @@ -196,6 +204,30 @@ class PaypalBackend(BackendBase): subscr.save() raise + def cancel_subscription(self, subscr): + if not subscr.backend_extid: + return False + + try: + r = requests.post(self.nvp_api_base, data={ + "METHOD": "ManageRecurringPaymentsProfileStatus", + "PROFILEID": subscr.backend_extid, + "ACTION": "cancel", + "USER": self.api_username, + "PWD": self.api_password, + "SIGNATURE": self.api_sig, + "VERSION": "204.0", + }) + r.raise_for_status() + print(r.text) + + subscr.status = 'cancelled' + subscr.save() + return True + except Exception as e: + print(e) + return False + def get_ext_url(self, payment): if not payment.backend_extid: return None diff --git a/payments/backends/stripe.py b/payments/backends/stripe.py index b8c4d1e..efad163 100644 --- a/payments/backends/stripe.py +++ b/payments/backends/stripe.py @@ -123,6 +123,7 @@ class StripeBackend(BackendBase): subscr.status = 'cancelled' subscr.save() + return True def webhook_session_completed(self, event): session = event['data']['object'] diff --git a/payments/migrations/0006_auto_20190907_2029.py b/payments/migrations/0006_auto_20190907_2029.py new file mode 100644 index 0000000..d19c545 --- /dev/null +++ b/payments/migrations/0006_auto_20190907_2029.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.1 on 2019-09-07 20:29 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('payments', '0005_auto_20160907_0018'), + ] + + operations = [ + migrations.CreateModel( + name='Feedback', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('message', models.TextField()), + ('subscription', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='payments.Subscription')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/payments/models.py b/payments/models.py index ea92a0e..36566bf 100644 --- a/payments/models.py +++ b/payments/models.py @@ -55,11 +55,10 @@ ACTIVE_BACKEND_CHOICES = [] logger.info("loading payment backends...") for cls in BACKEND_CLASSES: name = cls.backend_id - prefix = "Backend {:<8}:".format(name) assert isinstance(name, str) if name not in backends_settings: - logger.info("%s ☒ disabled (no settings)", prefix) + logger.info("payments: ☐ %s disabled (no settings)", name) continue backend_settings = backends_settings.get(name, {}) @@ -68,7 +67,7 @@ for cls in BACKEND_CLASSES: backend_settings[k] = v() if not backend_settings.get('enabled'): - logger.info("%s ☒ disabled (by settings)", prefix) + logger.info("payments: ☐ %s disabled (by settings)", name) continue obj = cls(backend_settings) @@ -79,14 +78,14 @@ for cls in BACKEND_CLASSES: if obj.backend_enabled: ACTIVE_BACKENDS[name] = obj ACTIVE_BACKEND_CHOICES.append((name, cls.backend_verbose_name)) - logger.info("%s ☑ initialized", prefix) + logger.info("payments: ☑ %s initialized", name) else: - logger.info("%s ☒ disabled (initialization failed)", prefix) + logger.info("payments: ☒ %s disabled (initialization failed)", name) BACKEND_CHOICES = sorted(BACKEND_CHOICES, key=lambda x: x[0]) ACTIVE_BACKEND_CHOICES = sorted(ACTIVE_BACKEND_CHOICES, key=lambda x: x[0]) -logger.info("finished. %d/%d backends active", len(ACTIVE_BACKENDS), len(BACKEND_CLASSES)) +logger.info("payments: finished. %d/%d backends active", len(ACTIVE_BACKENDS), len(BACKEND_CLASSES)) def period_months(p): @@ -221,3 +220,9 @@ class Subscription(models.Model): return payment +class Feedback(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + subscription = models.ForeignKey('Subscription', null=True, blank=True, on_delete=models.SET_NULL) + created = models.DateTimeField(auto_now_add=True) + message = models.TextField() + diff --git a/payments/urls.py b/payments/urls.py index ea737cf..2567584 100644 --- a/payments/urls.py +++ b/payments/urls.py @@ -7,16 +7,15 @@ urlpatterns = [ url(r'^new$', views.new), url(r'^view/(?P[0-9]+)$', views.view, name='view'), url(r'^cancel/(?P[0-9]+)$', views.cancel, name='cancel'), - url(r'^cancel_subscr/(?P[0-9]+)$', views.cancel_subscr, name='cancel_subscr'), + url(r'^cancel_subscr/(?P[0-9]+)?$', views.cancel_subscr, name='cancel_subscr'), url(r'^return_subscr/(?P[0-9]+)$', views.return_subscr, name='return_subscr'), - url(r'^callback/paypal/(?P[0-9]+)$', views.callback_paypal, name='cb_paypal'), - url(r'^callback/coingate/(?P[0-9]+)$', views.callback_coingate, name='cb_coingate'), - url(r'^callback/stripe/(?P[0-9]+)$', views.callback_stripe, name='cb_stripe'), - url(r'^callback/coinbase/$', views.callback_coinbase, name='cb_coinbase'), - url(r'^callback/coinpayments/(?P[0-9]+)$', views.callback_coinpayments, name='cb_coinpayments'), - url(r'^callback/paypal_subscr/(?P[0-9]+)$', views.callback_paypal_subscr, name='cb_paypal_subscr'), - url(r'^callback/stripe_subscr/(?P[0-9]+)$', views.callback_stripe_subscr, name='cb_stripe_subscr'), + url(r'^callback/paypal/(?P[0-9]+)$', views.payment_callback('paypal'), name='cb_paypal'), + url(r'^callback/coingate/(?P[0-9]+)$', views.payment_callback('coingate'), name='cb_coingate'), + url(r'^callback/stripe/(?P[0-9]+)$', views.payment_callback('stripe'), name='cb_stripe'), + url(r'^callback/coinbase/$', views.plain_callback('coinbase'), name='cb_coinbase'), + url(r'^callback/coinpayments/(?P[0-9]+)$', views.payment_callback('coinpayments'), name='cb_coinpayments'), + url(r'^callback/paypal_subscr/(?P[0-9]+)$', views.sub_callback('paypal'), name='cb_paypal_subscr'), url(r'^callback/stripe_hook$', views.stripe_hook, name='stripe_hook'), diff --git a/payments/views.py b/payments/views.py index 9431a0f..4a797bb 100644 --- a/payments/views.py +++ b/payments/views.py @@ -10,7 +10,7 @@ from django.contrib import messages from django.utils.translation import ugettext_lazy as _ from .forms import NewPaymentForm -from .models import Payment, Subscription, BACKENDS, ACTIVE_BACKENDS +from .models import Payment, Subscription, BACKENDS, ACTIVE_BACKENDS, Feedback def require_backend(name): @@ -73,92 +73,45 @@ def new(request): return r +def plain_callback(backend_name, method='callback'): + @csrf_exempt + def callback(request): + backend = require_backend(backend_name) -@csrf_exempt -def callback_paypal(request, id): - """ PayPal IPN """ - backend = require_backend('paypal') + m = getattr(backend, method) + if m and m(Payment, request): + return HttpResponse() + else: + return HttpResponseBadRequest() - p = Payment.objects.get(id=id) - if backend.callback(p, request): - return HttpResponse() - else: - return HttpResponseBadRequest() + return callback +def payment_callback(backend_name): + @csrf_exempt + def callback(request, id): + backend = require_backend(backend_name) + p = Payment.objects.get(id=id) -@csrf_exempt -@login_required -def callback_stripe(request, id): - """ Stripe button POST """ - backend = require_backend('stripe') - - p = Payment.objects.get(id=id) - backend.callback(p, request) - return redirect(reverse('payments:view', args=(id,))) - - -@csrf_exempt -def callback_coingate(request, id): - """ CoinGate payment callback """ - backend = require_backend('coingate') + if backend.callback(p, request): + return HttpResponse() + else: + return HttpResponseBadRequest() - p = Payment.objects.get(id=id) - if backend.callback(p, request): - return HttpResponse() - else: - return HttpResponseBadRequest() + return callback +def sub_callback(backend_name): + @csrf_exempt + def callback(request, id): + backend = require_backend(backend_name) -@csrf_exempt -def callback_coinbase(request): - backend = require_backend('coinbase') + p = Subscription.objects.get(id=id) - if backend.callback(Payment, request): - return HttpResponse() - else: - return HttpResponseBadRequest() - - -@csrf_exempt -def callback_coinpayments(request, id): - """ CoinPayments payment callback """ - backend = require_backend('coinpayments') - - p = Payment.objects.get(id=id) - if backend.callback(p, request): - return HttpResponse() - else: - return HttpResponseBadRequest() - - -@csrf_exempt -def callback_paypal_subscr(request, id): - """ PayPal Subscription IPN """ - backend = require_backend('paypal') - - p = Subscription.objects.get(id=id) - if backend.callback_subscr(p, request): - return HttpResponse() - else: - return HttpResponseBadRequest() - - -@csrf_exempt -@login_required -def callback_stripe_subscr(request, id): - """ Stripe subscription form target """ - backend = require_backend('stripe') - - p = Subscription.objects.get(id=id) - backend.callback_subscr(p, request) - if p.status == 'error' or p.status == 'cancelled': - messages.add_message(request, messages.ERROR, - _("Error subscribing. It usually means you don't" - " have enough money available.")) - else: - messages.add_message(request, messages.INFO, _("Subscribed!")) - return redirect(reverse('account:index')) + if backend.callback_subscr(p, request): + return HttpResponse() + else: + return HttpResponseBadRequest() + return callback @csrf_exempt def stripe_hook(request): @@ -187,17 +140,30 @@ def cancel(request, id): @login_required -def cancel_subscr(request, id): - if request.method != 'POST': +def cancel_subscr(request, id=None): + if request.method == 'POST' and id: + p = Subscription.objects.get(id=id, user=request.user) + + # Saving any feedback note + feedback = request.POST.get('feedback') + if feedback: + feedback = feedback[:10000] + f = Feedback(user=request.user, subscription=p, message=feedback) + f.save() + + try: + if p.backend.cancel_subscription(p): + messages.add_message(request, messages.INFO, _("Subscription cancelled!")) + else: + messages.add_message(request, messages.ERROR, _("Could not cancel the subscription. It may have already been cancelled or caused an error.")) + except NotImplementedError: + pass return redirect('account:index') - p = Subscription.objects.get(id=id, user=request.user) - try: - p.backend.cancel_subscription(p) - messages.add_message(request, messages.INFO, _("Subscription cancelled!")) - except NotImplementedError: - pass - return redirect('account:index') + subscription = request.user.vpnuser.get_subscription(include_unconfirmed=True) + return render(request, 'payments/cancel_subscr.html', { + 'subscription': subscription, + }) @login_required @@ -217,4 +183,3 @@ def list_payments(request): objects = request.user.payment_set.exclude(status='cancelled', created__lte=cancelled_limit) return render(request, 'payments/list.html', dict(payments=objects)) - diff --git a/static/css/style.css b/static/css/style.css index b0b1c38..2587e06 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -300,6 +300,10 @@ a.pure-button-primary, a.pure-button-selected { padding: 0.5em 2em; } +.button-danger { + background-color: #A7332F; +} + form p.inputinfo { font-size: 0.8em; display: block; @@ -735,6 +739,20 @@ div.ticket-message-private { } +/***************************************************/ +/********************* Payments */ + +.payments-cancel-page hr { + margin: 2em 1em; + border-bottom: 0; +} +.payments-cancel-page .feedback { + width: 100%; +} +.payments-cancel-page .pure-controls { + text-align: center; +} + /***************************************************/ /********************* Live chat */ diff --git a/templates/lambdainst/account.html b/templates/lambdainst/account.html index ffb8fb0..12da6fb 100644 --- a/templates/lambdainst/account.html +++ b/templates/lambdainst/account.html @@ -23,28 +23,8 @@ {% blocktrans trimmed with until=subscription.next_renew|date:'DATE_FORMAT' backend=subscription.backend.backend_verbose_name %} Your account is active. Your subscription will automatically renew on {{until}} ({{backend}}). {% endblocktrans %} + ({% trans "cancel" %})

- {% if subscription.backend_id == 'paypal' %} -

{% trans 'You can cancel it from PayPal account.' %}

- {% else %} -
- {% csrf_token %} -
-
- -
-
-
- - {% endif %} {% else %}
+

{% trans 'Cancel Subscription' %}

+ + {% if subscription %} +
+ {% csrf_token %} +
+

+ {% trans "We're sorry to see you go." %}
+ {% trans "Would you like to tell us why, or leave any feedback so we can improve our service?" %} +

+ + +
+ +
+ +
+
+
+ {% else %} +

You do not have any active subscription to cancel.

+ {% endif %} +
+{% endblock %}