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
{% 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 %} + +
You do not have any active subscription to cancel.
+ {% endif %} +