You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
from django.db import models
|
|
from django.conf import settings
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from jsonfield import JSONField
|
|
|
|
from .backends import BackendBase
|
|
|
|
backend_settings = settings.PAYMENTS_BACKENDS
|
|
assert isinstance(backend_settings, dict)
|
|
|
|
CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY
|
|
|
|
STATUS_CHOICES = (
|
|
('new', _("Waiting for payment")),
|
|
('confirmed', _("Confirmed")),
|
|
('cancelled', _("Cancelled")),
|
|
('rejected', _("Rejected by processor")),
|
|
('error', _("Payment processing failed")),
|
|
)
|
|
|
|
PERIOD_CHOICES = (
|
|
('6m', _("Every 6 months")),
|
|
('1year', _("Yearly")),
|
|
)
|
|
|
|
# All known backends (classes)
|
|
BACKENDS = {}
|
|
BACKEND_CHOICES = []
|
|
|
|
# All enabled backends (configured instances)
|
|
ACTIVE_BACKENDS = {}
|
|
ACTIVE_BACKEND_CHOICES = []
|
|
|
|
for cls in BackendBase.__subclasses__():
|
|
name = cls.backend_id
|
|
assert isinstance(name, str)
|
|
|
|
obj = cls(backend_settings.get(name, {}))
|
|
if not obj.backend_enabled:
|
|
if name in backend_settings:
|
|
raise Exception("Invalid settings for payment backend %r" % name)
|
|
|
|
BACKENDS[name] = obj
|
|
BACKEND_CHOICES.append((name, cls.backend_verbose_name))
|
|
|
|
if obj.backend_enabled:
|
|
ACTIVE_BACKENDS[name] = obj
|
|
ACTIVE_BACKEND_CHOICES.append((name, cls.backend_verbose_name))
|
|
|
|
BACKEND_CHOICES = sorted(BACKEND_CHOICES, key=lambda x: x[0])
|
|
ACTIVE_BACKEND_CHOICES = sorted(ACTIVE_BACKEND_CHOICES, key=lambda x: x[0])
|
|
|
|
|
|
class Payment(models.Model):
|
|
""" Just a payment.
|
|
If recurring_source is not null, it has been automatically issued.
|
|
backend_id is the external transaction ID, backend_data is other
|
|
things that should only be used by the associated backend.
|
|
"""
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
backend_id = models.CharField(max_length=16, choices=BACKEND_CHOICES)
|
|
status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='new')
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
modified = models.DateTimeField(auto_now=True)
|
|
confirmed_on = models.DateTimeField(null=True, blank=True)
|
|
amount = models.IntegerField()
|
|
paid_amount = models.IntegerField(default=0)
|
|
time = models.DurationField()
|
|
recurring_source = models.ForeignKey('RecurringPaymentSource', null=True, blank=True)
|
|
status_message = models.TextField(blank=True, null=True)
|
|
|
|
backend_extid = models.CharField(max_length=64, null=True, blank=True)
|
|
backend_data = JSONField(blank=True)
|
|
|
|
@property
|
|
def currency_code(self):
|
|
return CURRENCY_CODE
|
|
|
|
@property
|
|
def currency_name(self):
|
|
return CURRENCY_NAME
|
|
|
|
@property
|
|
def backend(self):
|
|
""" Returns a global instance of the backend
|
|
:rtype: BackendBase
|
|
"""
|
|
return BACKENDS[self.backend_id]
|
|
|
|
def get_amount_display(self):
|
|
return '%.2f %s' % (self.amount / 100, CURRENCY_NAME)
|
|
|
|
@property
|
|
def is_confirmed(self):
|
|
return self.status == 'confirmed'
|
|
|
|
class Meta:
|
|
ordering = ('-created', )
|
|
|
|
|
|
class RecurringPaymentSource(models.Model):
|
|
""" Used as a source to periodically make Payments.
|
|
They use the same backends.
|
|
"""
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
backend = models.CharField(max_length=16, choices=BACKEND_CHOICES)
|
|
created = models.DateTimeField(auto_now_add=True)
|
|
modified = models.DateTimeField(auto_now=True)
|
|
period = models.CharField(max_length=16, choices=PERIOD_CHOICES)
|
|
last_confirmed_payment = models.DateTimeField(blank=True, null=True)
|
|
|
|
backend_id = models.CharField(max_length=64, null=True, blank=True)
|
|
backend_data = JSONField(blank=True)
|
|
|