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 %}
-
-
- {% endif %}
{% else %}
{% blocktrans trimmed with backend=subscription.backend.backend_verbose_name %}
diff --git a/templates/payments/cancel_subscr.html b/templates/payments/cancel_subscr.html
new file mode 100644
index 0000000..153fc7b
--- /dev/null
+++ b/templates/payments/cancel_subscr.html
@@ -0,0 +1,32 @@
+{% extends 'account_layout.html' %}
+{% load i18n %}
+{% load staticfiles %}
+
+{% block account_content %}
+
+
+
{% trans 'Cancel Subscription' %}
+
+ {% if subscription %}
+
+ {% else %}
+
You do not have any active subscription to cancel.
+ {% endif %}
+
+{% endblock %}