Improve subscr cancellation, add unusub feedback

master
Alice 5 years ago
parent 3a99cf850d
commit b3539ef829

@ -232,6 +232,9 @@ PAYMENTS_BACKENDS = {
# 'receiver': '', # PayPal primary address if different # 'receiver': '', # PayPal primary address if different
# 'currency': 'EUR', # 'currency': 'EUR',
# 'header_image': '', # 'header_image': '',
'api_username': ',
'api_password': '',
'api_sig': '',
}, },
'stripe': { 'stripe': {
'enabled': False, 'enabled': False,

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2016-04-07 01:32+0000\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
@ -264,19 +264,19 @@ msgstr "Config"
msgid "Status" msgid "Status"
msgstr "État" msgstr "État"
#: payments/admin.py:11 #: payments/admin.py:12
msgid "Mark as cancelled (do not actually cancel)" msgid "Mark as cancelled (do not actually cancel)"
msgstr "Marquer comme annulé (n'annule pas)" 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" msgid "Payment Data"
msgstr "Données de paiement" msgstr "Données de paiement"
#: payments/admin.py:59 #: payments/admin.py:60
msgid "Amount" msgid "Amount"
msgstr "Montant" msgstr "Montant"
#: payments/admin.py:63 #: payments/admin.py:64
msgid "Paid amount" msgid "Paid amount"
msgstr "Montant payé" msgstr "Montant payé"
@ -351,11 +351,11 @@ msgstr ""
"En attente de la confirmation par Paypal... Cette étape peut durer quelques " "En attente de la confirmation par Paypal... Cette étape peut durer quelques "
"minutes..." "minutes..."
#: payments/backends/paypal.py:13 payments/backends/paypal.py:14 #: payments/backends/paypal.py:14 payments/backends/paypal.py:15
msgid "PayPal" msgid "PayPal"
msgstr "PayPal" msgstr "PayPal"
#: payments/backends/paypal.py:51 #: payments/backends/paypal.py:59
msgid "" msgid ""
"Waiting for PayPal to confirm the transaction... It can take up to a few " "Waiting for PayPal to confirm the transaction... It can take up to a few "
"minutes..." "minutes..."
@ -416,20 +416,17 @@ msgid "Every year"
msgstr "Tous les ans" msgstr "Tous les ans"
#: payments/views.py:156 #: 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!" msgid "Subscription cancelled!"
msgstr "Abonnement annulé!" 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 #: templates/account_layout.html:12
msgid "Config Download" msgid "Config Download"
msgstr "Configuration" msgstr "Configuration"
@ -443,7 +440,7 @@ msgstr "Paiements"
msgid "Models in the %(name)s application" msgid "Models in the %(name)s application"
msgstr "" msgstr ""
#: templates/admin/index.html:31 templates/lambdainst/account.html:148 #: templates/admin/index.html:31 templates/lambdainst/account.html:128
msgid "Add" msgid "Add"
msgstr "Ajouter" msgstr "Ajouter"
@ -676,19 +673,11 @@ msgstr ""
"Votre compte est actif. Votre abonnement sera automatiquement renouvelé le " "Votre compte est actif. Votre abonnement sera automatiquement renouvelé le "
"%(until)s via %(backend)s." "%(until)s via %(backend)s."
#: templates/lambdainst/account.html:28 #: templates/lambdainst/account.html:26
msgid "You can cancel it from PayPal account." msgid "cancel"
msgstr "Vous pouvez annuler depuis votre compte PayPal." msgstr "annuler"
#: templates/lambdainst/account.html:35
msgid "Cancel Subscription"
msgstr "Annuler l'abonnement"
#: templates/lambdainst/account.html:43 #: templates/lambdainst/account.html:30
msgid "Do you really want to cancel your subscription?"
msgstr "Voulez-vous vraimer annuler votre abonnement ?"
#: templates/lambdainst/account.html:50
#, python-format #, python-format
msgid "" msgid ""
"Your subscription is waiting for confirmation by %(backend)s. It may take up " "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 " "Votre abonnement est en attente de confirmation par %(backend)s. Cette étape "
"peut prendre jusqu'à quelques minutes..." "peut prendre jusqu'à quelques minutes..."
#: templates/lambdainst/account.html:58 #: templates/lambdainst/account.html:38
#, python-format #, python-format
msgid "Your account is paid until %(until)s" msgid "Your account is paid until %(until)s"
msgstr "Votre compte est activé jusqu'au %(until)s" msgstr "Votre compte est activé jusqu'au %(until)s"
#: templates/lambdainst/account.html:61 #: templates/lambdainst/account.html:41
#, python-format #, python-format
msgid "(%(left)s left)" msgid "(%(left)s left)"
msgstr "(%(left)s restant)" msgstr "(%(left)s restant)"
#: templates/lambdainst/account.html:67 #: templates/lambdainst/account.html:47
msgid "" msgid ""
"You can activate your free trial account for two hours periods for up to one " "You can activate your free trial account for two hours periods for up to one "
"week, by clicking this button:" "week, by clicking this button:"
@ -715,75 +704,75 @@ msgstr ""
"Vous pouvez activez votre compte d'essai pour des périodes de deux heures " "Vous pouvez activez votre compte d'essai pour des périodes de deux heures "
"pendant jusqu'à une semaine avec ce bouton:" "pendant jusqu'à une semaine avec ce bouton:"
#: templates/lambdainst/account.html:76 #: templates/lambdainst/account.html:56
msgid "Activate" msgid "Activate"
msgstr "Activer" msgstr "Activer"
#: templates/lambdainst/account.html:93 #: templates/lambdainst/account.html:73
msgid "Your account is not paid." msgid "Your account is not paid."
msgstr "Votre compte n'est pas payé." 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" msgid "Subscription"
msgstr "Abonnement" msgstr "Abonnement"
#: templates/lambdainst/account.html:110 #: templates/lambdainst/account.html:90
msgid "Pay every" msgid "Pay every"
msgstr "Payer tous les" msgstr "Payer tous les"
#: templates/lambdainst/account.html:112 templates/lambdainst/account.html:113 #: templates/lambdainst/account.html:92 templates/lambdainst/account.html:93
#: templates/lambdainst/account.html:114 templates/lambdainst/account.html:151 #: templates/lambdainst/account.html:94 templates/lambdainst/account.html:131
#: templates/lambdainst/account.html:152 templates/lambdainst/account.html:153 #: templates/lambdainst/account.html:132 templates/lambdainst/account.html:133
msgid "months" msgid "months"
msgstr "mois" msgstr "mois"
#: templates/lambdainst/account.html:119 templates/lambdainst/account.html:158 #: templates/lambdainst/account.html:99 templates/lambdainst/account.html:138
msgid "by" msgid "by"
msgstr "par" msgstr "par"
#: templates/lambdainst/account.html:131 #: templates/lambdainst/account.html:111
msgid "Subscribe" msgid "Subscribe"
msgstr "S'abonner" msgstr "S'abonner"
#: templates/lambdainst/account.html:133 #: templates/lambdainst/account.html:113
msgid "You can cancel the recurring payment at any time." msgid "You can cancel the recurring payment at any time."
msgstr "Vous pouvez annuler le paiement récurrent à n'importe quel moment." 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" msgid "One-time payment"
msgstr "Paiement ponctuel" msgstr "Paiement ponctuel"
#: templates/lambdainst/account.html:150 #: templates/lambdainst/account.html:130
msgid "month" msgid "month"
msgstr "mois" msgstr "mois"
#: templates/lambdainst/account.html:170 #: templates/lambdainst/account.html:150
msgid "Buy Now" msgid "Buy Now"
msgstr "Acheter" msgstr "Acheter"
#: templates/lambdainst/account.html:172 #: templates/lambdainst/account.html:152
msgid "If you still have time, it will be added." msgid "If you still have time, it will be added."
msgstr "S'il vous reste du temps, il sera ajouté." msgstr "S'il vous reste du temps, il sera ajouté."
#: templates/lambdainst/account.html:183 #: templates/lambdainst/account.html:163
msgid "Gift code" msgid "Gift code"
msgstr "Code cadeau" msgstr "Code cadeau"
#: templates/lambdainst/account.html:189 #: templates/lambdainst/account.html:169
msgid "Redeem gift code" msgid "Redeem gift code"
msgstr "Récupérer le code cadeau" msgstr "Récupérer le code cadeau"
#: templates/lambdainst/account.html:199 #: templates/lambdainst/account.html:179
msgid "" msgid ""
"Get two weeks for free for every referral that takes at least one month!" "Get two weeks for free for every referral that takes at least one month!"
msgstr "" msgstr ""
"Gagnez deux semaines gratuites pour chaque client qui a utilisé ce lien !" "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" msgid "Share this link"
msgstr "Partagez ce lien" msgstr "Partagez ce lien"
#: templates/lambdainst/account.html:206 #: templates/lambdainst/account.html:186
msgid "tweet" msgid "tweet"
msgstr "" msgstr ""
@ -895,7 +884,7 @@ msgstr "IP Partagée"
msgid "Bandwidth" msgid "Bandwidth"
msgstr "Bande passante" 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" msgid "Open"
msgstr "Open" msgstr "Open"
@ -1000,7 +989,7 @@ msgid "Guides"
msgstr "Guides" msgstr "Guides"
#: templates/layout.html:57 templates/tickets/index.html:6 #: 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" msgid "Support"
msgstr "Support" msgstr "Support"
@ -1032,6 +1021,23 @@ msgstr "C'est open-source!"
msgid "Any question? <b>Chat with us</b>" msgid "Any question? <b>Chat with us</b>"
msgstr "Une question ? <b>Contactez nous</b>" msgstr "Une question ? <b>Contactez nous</b>"
#: 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 #: templates/payments/form.html:7 templates/payments/view.html:13
msgid "Payment" msgid "Payment"
msgstr "Paiement" msgstr "Paiement"
@ -1188,7 +1194,7 @@ msgstr "Nouvelle réponse à votre ticket:"
msgid "Open Ticket" msgid "Open Ticket"
msgstr "Créer le 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 #: tickets/views.py:116 tickets/views.py:146
msgid "Ticket:" msgid "Ticket:"
msgstr "Ticket:" msgstr "Ticket:"
@ -1221,7 +1227,7 @@ msgstr "Fermer les tickets séléctionnés"
msgid "Re-opened" msgid "Re-opened"
msgstr "Ré-ouvert" msgstr "Ré-ouvert"
#: tickets/admin.py:52 tickets/models.py:53 #: tickets/admin.py:52 tickets/models.py:52
msgid "Closed" msgid "Closed"
msgstr "Fermé" msgstr "Fermé"
@ -1233,30 +1239,45 @@ msgstr "Catégorie"
msgid "Message" msgid "Message"
msgstr "Message" msgstr "Message"
#: tickets/models.py:15 #: tickets/models.py:14
msgid "Security" msgid "Security"
msgstr "Sécurité" msgstr "Sécurité"
#: tickets/models.py:16 #: tickets/models.py:15
msgid "Account / Billing" msgid "Account / Billing"
msgstr "Compte / Facturation" msgstr "Compte / Facturation"
#: tickets/models.py:36 #: tickets/models.py:35
msgid "Can view any ticket" msgid "Can view any ticket"
msgstr "Peut voir n'importe quel ticket" msgstr "Peut voir n'importe quel ticket"
#: tickets/models.py:37 #: tickets/models.py:36
msgid "Can reply to any ticket" msgid "Can reply to any ticket"
msgstr "Peut répondre à n'importe quel ticket" msgstr "Peut répondre à n'importe quel ticket"
#: tickets/models.py:38 #: tickets/models.py:37
msgid "Can view private messages on tickets" msgid "Can view private messages on tickets"
msgstr "Peut voir les messages privés" msgstr "Peut voir les messages privés"
#: tickets/models.py:39 #: tickets/models.py:38
msgid "Can post private messages on tickets" msgid "Can post private messages on tickets"
msgstr "Peut envoyer des messages privés" msgstr "Peut envoyer des messages privés"
#: tickets/models.py:56 #: tickets/models.py:55
msgid "Waiting for staff" msgid "Waiting for staff"
msgstr "En attente du support" 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 ?"

@ -3,7 +3,8 @@ from django.shortcuts import resolve_url
from django.contrib import admin from django.contrib import admin
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _ 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): def subscr_mark_as_cancelled(modeladmin, request, queryset):
@ -116,6 +117,15 @@ class SubscriptionAdmin(admin.ModelAdmin):
return link(object.backend_extid, ext_url) return link(object.backend_extid, ext_url)
backend_extid_link.allow_tags = True 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(Payment, PaymentAdmin)
admin.site.register(Subscription, SubscriptionAdmin) admin.site.register(Subscription, SubscriptionAdmin)
admin.site.register(Feedback, FeedbackAdmin)

@ -4,6 +4,7 @@ from urllib.parse import urlencode
from urllib.request import urlopen from urllib.request import urlopen
from django.urls import reverse from django.urls import reverse
from django.conf import settings as project_settings from django.conf import settings as project_settings
import requests
from .base import BackendBase from .base import BackendBase
@ -22,13 +23,20 @@ class PaypalBackend(BackendBase):
self.account_address = settings.get('address') self.account_address = settings.get('address')
self.receiver_address = settings.get('receiver', self.account_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: if self.test:
default_nvp = 'https://api-3t.sandbox.paypal.com/nvp'
default_api = 'https://www.sandbox.paypal.com/' default_api = 'https://www.sandbox.paypal.com/'
else: else:
default_nvp = 'https://api-3t.paypal.com/nvp'
default_api = 'https://www.paypal.com/' default_api = 'https://www.paypal.com/'
self.api_base = settings.get('api_base', default_api) 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 self.backend_enabled = True
def new_payment(self, payment): def new_payment(self, payment):
@ -196,6 +204,30 @@ class PaypalBackend(BackendBase):
subscr.save() subscr.save()
raise 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): def get_ext_url(self, payment):
if not payment.backend_extid: if not payment.backend_extid:
return None return None

@ -123,6 +123,7 @@ class StripeBackend(BackendBase):
subscr.status = 'cancelled' subscr.status = 'cancelled'
subscr.save() subscr.save()
return True
def webhook_session_completed(self, event): def webhook_session_completed(self, event):
session = event['data']['object'] session = event['data']['object']

@ -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)),
],
),
]

@ -55,11 +55,10 @@ ACTIVE_BACKEND_CHOICES = []
logger.info("loading payment backends...") logger.info("loading payment backends...")
for cls in BACKEND_CLASSES: for cls in BACKEND_CLASSES:
name = cls.backend_id name = cls.backend_id
prefix = "Backend {:<8}:".format(name)
assert isinstance(name, str) assert isinstance(name, str)
if name not in backends_settings: if name not in backends_settings:
logger.info("%s disabled (no settings)", prefix) logger.info("payments: ☐ %s disabled (no settings)", name)
continue continue
backend_settings = backends_settings.get(name, {}) backend_settings = backends_settings.get(name, {})
@ -68,7 +67,7 @@ for cls in BACKEND_CLASSES:
backend_settings[k] = v() backend_settings[k] = v()
if not backend_settings.get('enabled'): if not backend_settings.get('enabled'):
logger.info("%s disabled (by settings)", prefix) logger.info("payments: ☐ %s disabled (by settings)", name)
continue continue
obj = cls(backend_settings) obj = cls(backend_settings)
@ -79,14 +78,14 @@ for cls in BACKEND_CLASSES:
if obj.backend_enabled: if obj.backend_enabled:
ACTIVE_BACKENDS[name] = obj ACTIVE_BACKENDS[name] = obj
ACTIVE_BACKEND_CHOICES.append((name, cls.backend_verbose_name)) ACTIVE_BACKEND_CHOICES.append((name, cls.backend_verbose_name))
logger.info("%s ☑ initialized", prefix) logger.info("payments: ☑ %s initialized", name)
else: 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]) BACKEND_CHOICES = sorted(BACKEND_CHOICES, key=lambda x: x[0])
ACTIVE_BACKEND_CHOICES = sorted(ACTIVE_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): def period_months(p):
@ -221,3 +220,9 @@ class Subscription(models.Model):
return payment 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()

@ -7,16 +7,15 @@ urlpatterns = [
url(r'^new$', views.new), url(r'^new$', views.new),
url(r'^view/(?P<id>[0-9]+)$', views.view, name='view'), url(r'^view/(?P<id>[0-9]+)$', views.view, name='view'),
url(r'^cancel/(?P<id>[0-9]+)$', views.cancel, name='cancel'), url(r'^cancel/(?P<id>[0-9]+)$', views.cancel, name='cancel'),
url(r'^cancel_subscr/(?P<id>[0-9]+)$', views.cancel_subscr, name='cancel_subscr'), url(r'^cancel_subscr/(?P<id>[0-9]+)?$', views.cancel_subscr, name='cancel_subscr'),
url(r'^return_subscr/(?P<id>[0-9]+)$', views.return_subscr, name='return_subscr'), url(r'^return_subscr/(?P<id>[0-9]+)$', views.return_subscr, name='return_subscr'),
url(r'^callback/paypal/(?P<id>[0-9]+)$', views.callback_paypal, name='cb_paypal'), url(r'^callback/paypal/(?P<id>[0-9]+)$', views.payment_callback('paypal'), name='cb_paypal'),
url(r'^callback/coingate/(?P<id>[0-9]+)$', views.callback_coingate, name='cb_coingate'), url(r'^callback/coingate/(?P<id>[0-9]+)$', views.payment_callback('coingate'), name='cb_coingate'),
url(r'^callback/stripe/(?P<id>[0-9]+)$', views.callback_stripe, name='cb_stripe'), url(r'^callback/stripe/(?P<id>[0-9]+)$', views.payment_callback('stripe'), name='cb_stripe'),
url(r'^callback/coinbase/$', views.callback_coinbase, name='cb_coinbase'), url(r'^callback/coinbase/$', views.plain_callback('coinbase'), name='cb_coinbase'),
url(r'^callback/coinpayments/(?P<id>[0-9]+)$', views.callback_coinpayments, name='cb_coinpayments'), url(r'^callback/coinpayments/(?P<id>[0-9]+)$', views.payment_callback('coinpayments'), name='cb_coinpayments'),
url(r'^callback/paypal_subscr/(?P<id>[0-9]+)$', views.callback_paypal_subscr, name='cb_paypal_subscr'), url(r'^callback/paypal_subscr/(?P<id>[0-9]+)$', views.sub_callback('paypal'), name='cb_paypal_subscr'),
url(r'^callback/stripe_subscr/(?P<id>[0-9]+)$', views.callback_stripe_subscr, name='cb_stripe_subscr'),
url(r'^callback/stripe_hook$', views.stripe_hook, name='stripe_hook'), url(r'^callback/stripe_hook$', views.stripe_hook, name='stripe_hook'),

@ -10,7 +10,7 @@ from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .forms import NewPaymentForm 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): def require_backend(name):
@ -73,92 +73,45 @@ def new(request):
return r return r
def plain_callback(backend_name, method='callback'):
@csrf_exempt
def callback(request):
backend = require_backend(backend_name)
@csrf_exempt m = getattr(backend, method)
def callback_paypal(request, id): if m and m(Payment, request):
""" PayPal IPN """
backend = require_backend('paypal')
p = Payment.objects.get(id=id)
if backend.callback(p, request):
return HttpResponse() return HttpResponse()
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
return callback
@csrf_exempt def payment_callback(backend_name):
@login_required @csrf_exempt
def callback_stripe(request, id): def callback(request, id):
""" Stripe button POST """ backend = require_backend(backend_name)
backend = require_backend('stripe')
p = Payment.objects.get(id=id) 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')
p = Payment.objects.get(id=id)
if backend.callback(p, request):
return HttpResponse()
else:
return HttpResponseBadRequest()
@csrf_exempt
def callback_coinbase(request):
backend = require_backend('coinbase')
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): if backend.callback(p, request):
return HttpResponse() return HttpResponse()
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
return callback
@csrf_exempt def sub_callback(backend_name):
def callback_paypal_subscr(request, id): @csrf_exempt
""" PayPal Subscription IPN """ def callback(request, id):
backend = require_backend('paypal') backend = require_backend(backend_name)
p = Subscription.objects.get(id=id) p = Subscription.objects.get(id=id)
if backend.callback_subscr(p, request): if backend.callback_subscr(p, request):
return HttpResponse() return HttpResponse()
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
return callback
@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'))
@csrf_exempt @csrf_exempt
def stripe_hook(request): def stripe_hook(request):
@ -187,18 +140,31 @@ def cancel(request, id):
@login_required @login_required
def cancel_subscr(request, id): def cancel_subscr(request, id=None):
if request.method != 'POST': if request.method == 'POST' and id:
return redirect('account:index')
p = Subscription.objects.get(id=id, user=request.user) 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: try:
p.backend.cancel_subscription(p) if p.backend.cancel_subscription(p):
messages.add_message(request, messages.INFO, _("Subscription cancelled!")) 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: except NotImplementedError:
pass pass
return redirect('account:index') return redirect('account:index')
subscription = request.user.vpnuser.get_subscription(include_unconfirmed=True)
return render(request, 'payments/cancel_subscr.html', {
'subscription': subscription,
})
@login_required @login_required
def return_subscr(request, id): def return_subscr(request, id):
@ -217,4 +183,3 @@ def list_payments(request):
objects = request.user.payment_set.exclude(status='cancelled', objects = request.user.payment_set.exclude(status='cancelled',
created__lte=cancelled_limit) created__lte=cancelled_limit)
return render(request, 'payments/list.html', dict(payments=objects)) return render(request, 'payments/list.html', dict(payments=objects))

@ -300,6 +300,10 @@ a.pure-button-primary, a.pure-button-selected {
padding: 0.5em 2em; padding: 0.5em 2em;
} }
.button-danger {
background-color: #A7332F;
}
form p.inputinfo { form p.inputinfo {
font-size: 0.8em; font-size: 0.8em;
display: block; 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 */ /********************* Live chat */

@ -23,28 +23,8 @@
{% blocktrans trimmed with until=subscription.next_renew|date:'DATE_FORMAT' backend=subscription.backend.backend_verbose_name %} {% 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}}). Your account is active. Your subscription will automatically renew on {{until}} ({{backend}}).
{% endblocktrans %} {% endblocktrans %}
(<a href="{% url 'payments:cancel_subscr' %}">{% trans "cancel" %}</a>)
</p> </p>
{% if subscription.backend_id == 'paypal' %}
<p>{% trans 'You can cancel it from PayPal account.' %}</p>
{% else %}
<form action="/payments/cancel_subscr/{{subscription.id}}" method="post" class="pure-form centered-form" id="cancel-form">
{% csrf_token %}
<fieldset>
<div class="pure-controls">
<input type="submit" class="pure-button pure-button-primary"
value="{% trans 'Cancel Subscription' %}" />
</div>
</fieldset>
</form>
<script type="text/javascript">
(function() {
var e = document.getElementById("cancel-form");
e.onsubmit = function() {
return confirm("{% trans 'Do you really want to cancel your subscription?' %}");
};
})();
</script>
{% endif %}
{% else %} {% else %}
<p class="account-status-paid"> <p class="account-status-paid">
{% blocktrans trimmed with backend=subscription.backend.backend_verbose_name %} {% blocktrans trimmed with backend=subscription.backend.backend_verbose_name %}

@ -0,0 +1,32 @@
{% extends 'account_layout.html' %}
{% load i18n %}
{% load staticfiles %}
{% block account_content %}
<div class="payments-cancel-page">
<h1>{% trans 'Cancel Subscription' %}</h1>
{% if subscription %}
<form action="/payments/cancel_subscr/{{subscription.id}}" method="post" class="pure-form centered-form" id="cancel-form">
{% csrf_token %}
<fieldset>
<p>
{% trans "We're sorry to see you go." %}<br />
{% trans "Would you like to tell us why, or leave any feedback so we can improve our service?" %}
</p>
<textarea name="feedback" class="pure-input-1" maxlength=10000></textarea>
<hr />
<div class="pure-controls">
<input type="submit" class="pure-button pure-button-primary button-danger"
value="{% trans 'Cancel Subscription' %}" />
</div>
</fieldset>
</form>
{% else %}
<p>You do not have any active subscription to cancel.</p>
{% endif %}
</div>
{% endblock %}
Loading…
Cancel
Save