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.

abstract_models.py 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. from decimal import Decimal
  2. from django.db import models
  3. from django.utils.translation import ugettext as _
  4. from django.conf import settings
  5. from oscar.core.compat import AUTH_USER_MODEL
  6. from oscar.core.utils import slugify
  7. class AbstractTransaction(models.Model):
  8. """
  9. A transaction with a payment gateway
  10. For example:
  11. * A 'pre-auth' with a bankcard gateway
  12. * A 'settle' with a credit provider (see django-oscar-accounts)
  13. """
  14. source = models.ForeignKey(
  15. 'payment.Source', related_name='transactions',
  16. verbose_name=_("Source"))
  17. # We define some sample types but don't constrain txn_type to be one of
  18. # these as there will be domain-specific ones that we can't anticipate
  19. # here.
  20. AUTHORISE, DEBIT, REFUND = 'Authorise', 'Debit', 'Refund'
  21. txn_type = models.CharField(_("Type"), max_length=128, blank=True)
  22. amount = models.DecimalField(_("Amount"), decimal_places=2, max_digits=12)
  23. reference = models.CharField(_("Reference"), max_length=128, blank=True)
  24. status = models.CharField(_("Status"), max_length=128, blank=True)
  25. date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
  26. def __unicode__(self):
  27. return _(u"%(type)s of %(amount).2f") % {
  28. 'type': self.txn_type,
  29. 'amount': self.amount}
  30. class Meta:
  31. abstract = True
  32. verbose_name = _("Transaction")
  33. verbose_name_plural = _("Transactions")
  34. class AbstractSource(models.Model):
  35. """
  36. A source of payment for an order.
  37. This is normally a credit card which has been pre-authed for the order
  38. amount, but some applications will allow orders to be paid for using
  39. multiple sources such as cheque, credit accounts, gift cards. Each payment
  40. source will have its own entry.
  41. This source object tracks how much money has been authorised, debited and
  42. refunded, which is useful when payment takes place in multiple stages.
  43. """
  44. order = models.ForeignKey(
  45. 'order.Order', related_name='sources', verbose_name=_("Order"))
  46. source_type = models.ForeignKey(
  47. 'payment.SourceType', verbose_name=_("Source Type"))
  48. currency = models.CharField(
  49. _("Currency"), max_length=12, default=settings.OSCAR_DEFAULT_CURRENCY)
  50. # Track the various amounts associated with this source
  51. amount_allocated = models.DecimalField(
  52. _("Amount Allocated"), decimal_places=2, max_digits=12,
  53. default=Decimal('0.00'))
  54. amount_debited = models.DecimalField(
  55. _("Amount Debited"), decimal_places=2, max_digits=12,
  56. default=Decimal('0.00'))
  57. amount_refunded = models.DecimalField(
  58. _("Amount Refunded"), decimal_places=2, max_digits=12,
  59. default=Decimal('0.00'))
  60. # Reference number for this payment source. This is often used to look up
  61. # a transaction model for a particular payment partner.
  62. reference = models.CharField(_("Reference"), max_length=128, blank=True)
  63. # A customer-friendly label for the source, eg XXXX-XXXX-XXXX-1234
  64. label = models.CharField(_("Label"), max_length=128, blank=True)
  65. # A dictionary of submission data that is stored as part of the
  66. # checkout process, where we need to pass an instance of this class around
  67. submission_data = None
  68. # We keep a list of deferred transactions that are only actually saved when
  69. # the source is saved for the first time
  70. deferred_txns = None
  71. class Meta:
  72. abstract = True
  73. verbose_name = _("Source")
  74. verbose_name_plural = _("Sources")
  75. def __unicode__(self):
  76. description = _("Allocation of %(amount).2f from type %(type)s") % {
  77. 'amount': self.amount_allocated, 'type': self.source_type}
  78. if self.reference:
  79. description += _(" (reference: %s)") % self.reference
  80. return description
  81. def save(self, *args, **kwargs):
  82. super(AbstractSource, self).save(*args, **kwargs)
  83. if self.deferred_txns:
  84. for txn in self.deferred_txns:
  85. self._create_transaction(*txn)
  86. def create_deferred_transaction(self, txn_type, amount, reference=None,
  87. status=None):
  88. """
  89. Register the data for a transaction that can't be created yet due to FK
  90. constraints. This happens at checkout where create an payment source
  91. and a transaction but can't save them until the order model exists.
  92. """
  93. if self.deferred_txns is None:
  94. self.deferred_txns = []
  95. self.deferred_txns.append((txn_type, amount, reference, status))
  96. def _create_transaction(self, txn_type, amount, reference=None,
  97. status=None):
  98. AbstractTransaction.objects.create(
  99. source=self, txn_type=txn_type, amount=amount,
  100. reference=reference, status=status)
  101. # =======
  102. # Actions
  103. # =======
  104. def allocate(self, amount, reference=None, status=None):
  105. """
  106. Convenience method for ring-fencing money against this source
  107. """
  108. self.amount_allocated += amount
  109. self.save()
  110. self._create_transaction(
  111. AbstractTransaction.AUTHORISE, amount, reference, status)
  112. allocate.alters_data = True
  113. def debit(self, amount=None, reference=None, status=None):
  114. """
  115. Convenience method for recording debits against this source
  116. """
  117. if amount is None:
  118. amount = self.balance()
  119. self.amount_debited += amount
  120. self.save()
  121. self._create_transaction(
  122. AbstractTransaction.DEBIT, amount, reference, status)
  123. debit.alters_data = True
  124. def refund(self, amount, reference=None, status=None):
  125. """
  126. Convenience method for recording refunds against this source
  127. """
  128. self.amount_refunded += amount
  129. self.save()
  130. self._create_transaction(
  131. AbstractTransaction.REFUND, amount, reference, status)
  132. refund.alters_data = True
  133. # ==========
  134. # Properties
  135. # ==========
  136. @property
  137. def balance(self):
  138. """
  139. Return the balance of this source
  140. """
  141. return (self.amount_allocated - self.amount_debited +
  142. self.amount_refunded)
  143. @property
  144. def amount_available_for_refund(self):
  145. """
  146. Return the amount available to be refunded
  147. """
  148. return self.amount_debited - self.amount_refunded
  149. class AbstractSourceType(models.Model):
  150. """
  151. A type of payment source.
  152. This could be an external partner like PayPal or DataCash,
  153. or an internal source such as a managed account.
  154. """
  155. name = models.CharField(_("Name"), max_length=128)
  156. code = models.SlugField(
  157. _("Code"), max_length=128,
  158. help_text=_("This is used within forms to identify this source type"))
  159. class Meta:
  160. abstract = True
  161. verbose_name = _("Source Type")
  162. verbose_name_plural = _("Source Types")
  163. def __unicode__(self):
  164. return self.name
  165. def save(self, *args, **kwargs):
  166. if not self.code:
  167. self.code = slugify(self.name)
  168. super(AbstractSourceType, self).save(*args, **kwargs)
  169. class AbstractBankcard(models.Model):
  170. user = models.ForeignKey(AUTH_USER_MODEL, related_name='bankcards',
  171. verbose_name=_("User"))
  172. card_type = models.CharField(_("Card Type"), max_length=128)
  173. name = models.CharField(_("Name"), max_length=255)
  174. number = models.CharField(_("Number"), max_length=32)
  175. expiry_date = models.DateField(_("Expiry Date"))
  176. # For payment partners who are storing the full card details for us
  177. partner_reference = models.CharField(
  178. _("Partner Reference"), max_length=255, blank=True)
  179. class Meta:
  180. abstract = True
  181. verbose_name = _("Bankcard")
  182. verbose_name_plural = _("Bankcards")
  183. def save(self, *args, **kwargs):
  184. self.number = self._get_obfuscated_number()
  185. super(AbstractBankcard, self).save(*args, **kwargs)
  186. def _get_obfuscated_number(self):
  187. return u"XXXX-XXXX-XXXX-%s" % self.number[-4:]