You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

models.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from decimal import Decimal
  2. from django.db import models
  3. from django.template.defaultfilters import slugify
  4. from django.utils.translation import ugettext as _
  5. from django.conf import settings
  6. class Transaction(models.Model):
  7. """
  8. A transaction for payment sources which need a secondary 'transaction' to actually take the money
  9. This applies mainly to credit card sources which can be a pre-auth for the money. A 'complete'
  10. needs to be run later to debit the money from the account.
  11. """
  12. source = models.ForeignKey('payment.Source', related_name='transactions')
  13. # We define some sample types
  14. AUTHORISE, DEBIT, REFUND = 'Authorise', 'Debit', 'Refund'
  15. txn_type = models.CharField(max_length=128, blank=True)
  16. amount = models.DecimalField(decimal_places=2, max_digits=12)
  17. reference = models.CharField(max_length=128, null=True)
  18. status = models.CharField(max_length=128, null=True)
  19. date_created = models.DateTimeField(auto_now_add=True)
  20. def __unicode__(self):
  21. return "%s of %.2f" % (self.txn_type, self.amount)
  22. class Source(models.Model):
  23. """
  24. A source of payment for an order.
  25. This is normally a credit card which has been pre-authed for the order
  26. amount, but some applications will allow orders to be paid for using
  27. multiple sources such as cheque, credit accounts, gift cards. Each payment
  28. source will have its own entry.
  29. """
  30. order = models.ForeignKey('order.Order', related_name='sources')
  31. source_type = models.ForeignKey('payment.SourceType')
  32. currency = models.CharField(max_length=12, default=settings.OSCAR_DEFAULT_CURRENCY)
  33. # Track the various amounts associated with this source
  34. amount_allocated = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))
  35. amount_debited = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))
  36. amount_refunded = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))
  37. # Reference number for this payment source. This is often used to look up a
  38. # transaction model for a particular payment partner.
  39. reference = models.CharField(max_length=128, blank=True, null=True)
  40. # A customer-friendly label for the source, eg XXXX-XXXX-XXXX-1234
  41. label = models.CharField(max_length=128, blank=True, null=True)
  42. # A dictionary of submission data that is stored as part of the
  43. # checkout process.
  44. submission_data = None
  45. # We keep a list of deferred transactions that are only actually saved when
  46. # the source is saved for the first time
  47. deferred_txns = None
  48. def __unicode__(self):
  49. description = "Allocation of %.2f from type %s" % (self.amount_allocated, self.source_type)
  50. if self.reference:
  51. description += " (reference: %s)" % self.reference
  52. return description
  53. def save(self, *args, **kwargs):
  54. super(Source, self).save(*args, **kwargs)
  55. if self.deferred_txns:
  56. for txn in self.deferred_txns:
  57. self._create_transaction(*txn)
  58. def balance(self):
  59. return self.amount_allocated - self.amount_debited + self.amount_refunded
  60. def create_deferred_transaction(self, txn_type, amount, reference=None, status=None):
  61. """
  62. Register the data for a transaction that can't be created yet due to FK
  63. constraints. This happens at checkout where create an payment source and a
  64. transaction but can't save them until the order model exists.
  65. """
  66. if self.deferred_txns is None:
  67. self.deferred_txns = []
  68. self.deferred_txns.append((txn_type, amount, reference, status))
  69. def _create_transaction(self, txn_type, amount, reference=None, status=None):
  70. Transaction.objects.create(source=self,
  71. txn_type=txn_type,
  72. amount=amount,
  73. reference=reference,
  74. status=status)
  75. def allocate(self, amount, reference=None, status=None):
  76. """
  77. Convenience method for ring-fencing money against this source
  78. """
  79. self.amount_allocated += amount
  80. self.save()
  81. self._create_transaction(Transaction.AUTHORISE, amount, reference, status)
  82. def debit(self, amount=None, reference=None, status=None):
  83. """
  84. Convenience method for recording debits against this source
  85. """
  86. if amount is None:
  87. amount = self.balance()
  88. self.amount_debited += amount
  89. self.save()
  90. self._create_transaction(Transaction.DEBIT, amount, reference, status)
  91. def refund(self, amount, reference=None, status=None):
  92. """
  93. Convenience method for recording refunds against this source
  94. """
  95. self.amount_refunded += amount
  96. self.save()
  97. self._create_transaction(Transaction.REFUND, amount, reference, status)
  98. @property
  99. def amount_available_for_refund(self):
  100. """
  101. Return the amount available to be refunded
  102. """
  103. return self.amount_debited - self.amount_refunded
  104. class SourceType(models.Model):
  105. """
  106. A type of payment source.
  107. This could be an external partner like PayPal or DataCash,
  108. or an internal source such as a managed account.i
  109. """
  110. name = models.CharField(max_length=128)
  111. code = models.SlugField(max_length=128, help_text=_("""This is used within
  112. forms to identify this source type"""))
  113. def __unicode__(self):
  114. return self.name
  115. def save(self, *args, **kwargs):
  116. if not self.code:
  117. self.code = slugify(self.name)
  118. super(SourceType, self).save(*args, **kwargs)
  119. class Bankcard(models.Model):
  120. user = models.ForeignKey('auth.User', related_name='bankcards')
  121. card_type = models.CharField(max_length=128)
  122. name = models.CharField(max_length=255)
  123. number = models.CharField(max_length=32)
  124. expiry_date = models.DateField()
  125. # For payment partners who are storing the full card details for us
  126. partner_reference = models.CharField(max_length=255, null=True, blank=True)
  127. def save(self, *args, **kwargs):
  128. self.number = self._get_obfuscated_number()
  129. super(Bankcard, self).save(*args, **kwargs)
  130. def _get_obfuscated_number(self):
  131. return u"XXXX-XXXX-XXXX-%s" % self.number[-4:]