django-orchestra-test/orchestra/contrib/payments/models.py

205 lines
6.9 KiB
Python
Raw Normal View History

from django.core.exceptions import ValidationError
2014-07-23 16:24:56 +00:00
from django.db import models
from django.utils.functional import cached_property
2014-07-23 16:24:56 +00:00
from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
from orchestra.models.fields import PrivateFileField
2014-09-04 15:55:43 +00:00
from orchestra.models.queryset import group_by
2014-07-23 16:24:56 +00:00
from . import settings
from .methods import PaymentMethod
2014-09-04 15:55:43 +00:00
class PaymentSourcesQueryset(models.QuerySet):
def get_default(self):
2014-09-04 15:55:43 +00:00
return self.filter(is_active=True).first()
2014-07-23 16:24:56 +00:00
class PaymentSource(models.Model):
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
2015-04-05 10:46:24 +00:00
related_name='paymentsources')
2014-07-23 16:24:56 +00:00
method = models.CharField(_("method"), max_length=32,
2015-04-05 10:46:24 +00:00
choices=PaymentMethod.get_choices())
2015-03-29 16:10:07 +00:00
data = JSONField(_("data"), default={})
2014-09-30 10:20:11 +00:00
is_active = models.BooleanField(_("active"), default=True)
2014-09-04 15:55:43 +00:00
objects = PaymentSourcesQueryset.as_manager()
2015-04-02 16:14:55 +00:00
def __str__(self):
2014-09-04 15:55:43 +00:00
return "%s (%s)" % (self.label, self.method_class.verbose_name)
@cached_property
def method_class(self):
return PaymentMethod.get(self.method)
2015-03-04 21:06:16 +00:00
@cached_property
def method_instance(self):
2015-03-04 21:06:16 +00:00
""" Per request lived method_instance """
return self.method_class(self)
2015-03-04 21:06:16 +00:00
@cached_property
def label(self):
return self.method_instance.get_label()
@cached_property
def number(self):
return self.method_instance.get_number()
def get_bill_context(self):
2015-03-04 21:06:16 +00:00
method = self.method_instance
return {
'message': method.get_bill_message(),
}
def get_due_delta(self):
2015-03-04 21:06:16 +00:00
return self.method_instance.due_delta
2014-10-30 16:34:02 +00:00
def clean(self):
self.data = self.method_instance.clean_data()
2014-09-04 15:55:43 +00:00
class TransactionQuerySet(models.QuerySet):
group_by = group_by
2014-09-06 10:56:30 +00:00
def create(self, **kwargs):
source = kwargs.get('source')
if source is None or not hasattr(source.method_class, 'process'):
# Manual payments don't need processing
2015-07-09 10:44:22 +00:00
kwargs['state'] = self.model.WAITTING_EXECUTION
amount = kwargs.get('amount')
if amount == 0:
kwargs['state'] = self.model.SECURED
2014-09-06 10:56:30 +00:00
return super(TransactionQuerySet, self).create(**kwargs)
2014-09-18 15:07:39 +00:00
def secured(self):
return self.filter(state=Transaction.SECURED)
def exclude_rejected(self):
return self.exclude(state=Transaction.REJECTED)
def amount(self):
2015-06-22 14:14:16 +00:00
return next(iter(self.aggregate(models.Sum('amount')).values())) or 0
2014-09-18 15:07:39 +00:00
def processing(self):
return self.filter(state__in=[Transaction.EXECUTED, Transaction.WAITTING_EXECUTION])
2014-07-23 16:24:56 +00:00
class Transaction(models.Model):
2014-09-06 10:56:30 +00:00
WAITTING_PROCESSING = 'WAITTING_PROCESSING' # CREATED
2014-09-18 15:07:39 +00:00
WAITTING_EXECUTION = 'WAITTING_EXECUTION' # PROCESSED
2014-09-16 17:14:24 +00:00
EXECUTED = 'EXECUTED'
SECURED = 'SECURED'
2014-09-16 17:14:24 +00:00
REJECTED = 'REJECTED'
2014-07-23 16:24:56 +00:00
STATES = (
2014-07-24 09:53:34 +00:00
(WAITTING_PROCESSING, _("Waitting processing")),
2014-09-18 15:07:39 +00:00
(WAITTING_EXECUTION, _("Waitting execution")),
2014-09-16 17:14:24 +00:00
(EXECUTED, _("Executed")),
(SECURED, _("Secured")),
2014-09-16 17:14:24 +00:00
(REJECTED, _("Rejected")),
2014-07-23 16:24:56 +00:00
)
bill = models.ForeignKey('bills.bill', verbose_name=_("bill"),
2015-04-05 10:46:24 +00:00
related_name='transactions')
source = models.ForeignKey(PaymentSource, null=True, blank=True,
2015-04-05 10:46:24 +00:00
verbose_name=_("source"), related_name='transactions')
2015-07-09 10:44:22 +00:00
process = models.ForeignKey('payments.TransactionProcess', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name=_("process"), related_name='transactions')
2014-07-23 16:24:56 +00:00
state = models.CharField(_("state"), max_length=32, choices=STATES,
2015-04-05 10:46:24 +00:00
default=WAITTING_PROCESSING)
2014-07-23 16:24:56 +00:00
amount = models.DecimalField(_("amount"), max_digits=12, decimal_places=2)
currency = models.CharField(max_length=10, default=settings.PAYMENT_CURRENCY)
2014-09-26 15:05:20 +00:00
created_at = models.DateTimeField(_("created"), auto_now_add=True)
modified_at = models.DateTimeField(_("modified"), auto_now=True)
2014-07-24 09:53:34 +00:00
2014-09-18 15:07:39 +00:00
objects = TransactionQuerySet.as_manager()
2015-04-02 16:14:55 +00:00
def __str__(self):
2015-05-30 14:44:05 +00:00
return "#%i" % self.id
2014-09-04 15:55:43 +00:00
@property
def account(self):
return self.bill.account
2014-09-16 17:14:24 +00:00
2014-09-18 15:07:39 +00:00
def clean(self):
if not self.pk:
amount = self.bill.transactions.exclude(state=self.REJECTED).amount()
if amount >= self.bill.total:
2014-11-05 20:22:01 +00:00
raise ValidationError(_("New transactions can not be allocated for this bill."))
2014-09-18 15:07:39 +00:00
2015-04-04 17:44:07 +00:00
def check_state(self, *args):
2014-11-27 19:17:26 +00:00
if self.state not in args:
raise TypeError("Transaction not in %s" % ' or '.join(args))
2014-09-18 15:07:39 +00:00
def mark_as_processed(self):
2014-11-27 19:17:26 +00:00
self.check_state(self.WAITTING_PROCESSING)
2014-09-18 15:07:39 +00:00
self.state = self.WAITTING_EXECUTION
2015-06-03 12:49:30 +00:00
self.save(update_fields=['state', 'modified_at'])
2014-09-18 15:07:39 +00:00
2014-09-16 17:14:24 +00:00
def mark_as_executed(self):
2014-11-27 19:17:26 +00:00
self.check_state(self.WAITTING_EXECUTION)
2014-09-16 17:14:24 +00:00
self.state = self.EXECUTED
2015-06-03 12:49:30 +00:00
self.save(update_fields=['state', 'modified_at'])
2014-09-16 17:14:24 +00:00
def mark_as_secured(self):
2014-11-27 19:17:26 +00:00
self.check_state(self.EXECUTED)
2014-09-16 17:14:24 +00:00
self.state = self.SECURED
2015-06-03 12:49:30 +00:00
self.save(update_fields=['state', 'modified_at'])
2014-09-16 17:14:24 +00:00
def mark_as_rejected(self):
self.state = self.REJECTED
2015-06-03 12:49:30 +00:00
self.save(update_fields=['state', 'modified_at'])
class TransactionProcess(models.Model):
2014-07-29 20:10:37 +00:00
"""
Stores arbitrary data generated by payment methods while processing transactions
"""
2014-09-18 15:07:39 +00:00
CREATED = 'CREATED'
EXECUTED = 'EXECUTED'
ABORTED = 'ABORTED'
COMMITED = 'COMMITED'
STATES = (
(CREATED, _("Created")),
(EXECUTED, _("Executed")),
(ABORTED, _("Aborted")),
(COMMITED, _("Commited")),
)
2014-07-29 20:10:37 +00:00
data = JSONField(_("data"), blank=True)
file = PrivateFileField(_("file"), blank=True)
2014-09-18 15:07:39 +00:00
state = models.CharField(_("state"), max_length=16, choices=STATES, default=CREATED)
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
2014-09-18 15:07:39 +00:00
updated_at = models.DateTimeField(_("updated"), auto_now=True)
2014-07-29 20:10:37 +00:00
class Meta:
verbose_name_plural = _("Transaction processes")
2015-04-02 16:14:55 +00:00
def __str__(self):
2014-10-11 16:21:51 +00:00
return '#%i' % self.id
2014-09-18 15:07:39 +00:00
2015-04-04 17:44:07 +00:00
def check_state(self, *args):
2014-11-27 19:17:26 +00:00
if self.state not in args:
raise TypeError("Transaction process not in %s" % ' or '.join(args))
2014-09-18 15:07:39 +00:00
def mark_as_executed(self):
2014-11-27 19:17:26 +00:00
self.check_state(self.CREATED)
2014-09-18 15:07:39 +00:00
self.state = self.EXECUTED
for transaction in self.transactions.all():
transaction.mark_as_executed()
self.save(update_fields=['state'])
2014-09-18 15:07:39 +00:00
def abort(self):
2014-11-27 19:17:26 +00:00
self.check_state(self.CREATED, self.EXCECUTED)
2014-09-18 15:07:39 +00:00
self.state = self.ABORTED
for transaction in self.transaction.all():
transaction.mark_as_aborted()
self.save(update_fields=['state'])
2014-09-18 15:07:39 +00:00
def commit(self):
2014-11-27 19:17:26 +00:00
self.check_state(self.CREATED, self.EXECUTED)
2014-09-18 15:07:39 +00:00
self.state = self.COMMITED
for transaction in self.transactions.processing():
transaction.mark_as_secured()
self.save(update_fields=['state'])