Skip to content

Instantly share code, notes, and snippets.

@williln
Created January 30, 2019 19:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save williln/171542625d92f4944f79a90051ea3a21 to your computer and use it in GitHub Desktop.
Save williln/171542625d92f4944f79a90051ea3a21 to your computer and use it in GitHub Desktop.
import re
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.db import models
from django.template.loader import render_to_string
from django.utils.crypto import salted_hmac
from django.utils.translation import ugettext_lazy as _
from nanog.constants import MBR_TYPE_STANDARD
from service_store.constants import (
XACT_TYPES, XACT_REFUND, XACT_PURCHASE,
XACT_STATES, XACT_INCOMPLETE, XACT_REFUNDED,
XACT_DISPOSITIONS, XACT_NONE,
XACT_KINDS, XACT_KIND_INFO, )
class Transaction(models.Model):
"""
Description: Represents a single transaction in the store
"""
xact_type = models.CharField(_('type'),
max_length=20,
default=XACT_PURCHASE,
choices=XACT_TYPES,
help_text=_('Type of transaction -- purchase, refund, etc.'), )
state = models.CharField(_('state'),
max_length=20,
default=XACT_INCOMPLETE,
choices=XACT_STATES,
help_text=_('Current state of this transaction -- incomplete, complete, canceled, etc.'), )
disposition = models.CharField(_('disposition'),
max_length=20,
default=XACT_NONE,
choices=XACT_DISPOSITIONS,
help_text=_('The ultimate disposition of the transaction -- none, successfully completed, unsuccessful, or abnormal (partially completed but something was strange).'), )
explanation = models.CharField(_('explanation'),
max_length=384,
help_text=_('explanation for the disposition -- declined, amount mismatch, etc.'),
blank=True,
default='', )
membership_type = models.CharField(_('Kind of membership'),
help_text=_('is a student or regular membership'),
default=MBR_TYPE_STANDARD,
max_length=16, )
membership_years = models.IntegerField(_('Number of years'),
help_text=_('How many years\' worth of membership?'),
default=1, )
user = models.ForeignKey(User)
description = models.CharField(_('description'),
max_length=384,
help_text=_('description of transaction'), )
amount_billed = models.FloatField(_('amount billed'),
help_text=_('transaction amount that should be paid'),
default=0.00, )
amount_paid = models.FloatField(_('amount paid'),
help_text=_('transaction amount paid (or refunded) thru merchant account'),
default=0.00, )
cc_transaction_id = models.CharField(_('CC processor transaction id'),
null=True,
blank=True,
max_length=30,
help_text=_('CC processor transaction id for transaction'), )
authorization = models.CharField(_('authorization code'),
null=True,
blank=True,
max_length=120,
help_text=_('authorization code for completed transaction'), )
created = models.DateTimeField(_('created'),
auto_now_add=True,
help_text=_('Date transaction was created'), )
completed = models.DateTimeField(_('completed'),
auto_now=True,
help_text=_('Date transaction was completed'), )
last_updated = models.DateTimeField(_('last_updated'),
auto_now=True,
help_text=_('Date transaction was updated'),)
related_transaction = models.ForeignKey('Transaction',
null=True,
blank=True, )
notes = models.CharField(_('notes'), max_length=200,
help_text=_('notes'), blank=True, null=True, )
class Meta:
ordering = ['-completed', ]
verbose_name_plural = 'Membership Transactions'
verbose_name = 'Membership Transaction'
def __unicode__(self):
return u'%d (%s)' % (self.id, self.state, )
@property
def token(self):
"""Generates a token that hashes the current state of the transaction."""
key_salt = "store.confirm.token"
value = '%s.%s.%s.%s' % (
self.pk,
self.state,
self.user.pk,
self.amount_billed)
token = salted_hmac(key_salt, value).hexdigest()[::2]
return token
@property
def as_dict(self):
return self.__dict__.copy()
@property
def is_refundable(self):
"""Determines if a transaction is refundable - must not be
a refund already; must be a purchase type; must be for an
amount > 0.00"""
return (self.state != XACT_REFUNDED and
self.xact_type == XACT_PURCHASE and
self.amount_paid > 0.00)
def send_receipt(self):
"""Generate a receipt and send to the accountholder via email."""
site = Site.objects.get_current()
ctx_dict = {'transaction': self,
'contact_email': settings.RECEIPT_HELP_ADDRESS,
'site': site}
subject = render_to_string('store/email_receipt_subject.txt',
ctx_dict)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
message = render_to_string('store/email_receipt.txt',
ctx_dict)
send_mail(subject, message, settings.RECEIPT_EMAIL_FROM, (self.user.email, ))
class TransactionLog(models.Model):
"""
Description: Represents a log / history of a Transaction
"""
transaction = models.ForeignKey('Transaction')
message = models.CharField(_('message'), max_length=500,
help_text=_('log message'), )
user = models.ForeignKey(User)
timestamp = models.DateTimeField(auto_now=True)
kind = models.CharField(_('kind'),
max_length=10,
help_text=_('Log message kind'),
choices=XACT_KINDS,
default=XACT_KIND_INFO, )
class LegacyAMSTransaction(models.Model):
"""
Description: stuff from AMS transactions
"""
"""
"TRANSNO","EMAIL","TYPE","JOINED","ADJUSTMENT","TOTAL","RECEIVED","COMMENT1","COMMENT2"
"""
transno = models.IntegerField(_('AMS transaction number'),
blank=True,
null=True)
email = models.CharField(_('Email address'),
max_length=128,
blank=True,
null=True)
# ^^ matched to vv
user = models.ForeignKey(User, null=True, )
purchase_type = models.CharField(_('Purchase type'),
max_length=32,
blank=True,
null=True)
# date joined vvv
created = models.DateTimeField(_('created'),
auto_now=True,
help_text=_('Date transaction was created'), )
# amount received
amount = models.FloatField(_('amount'),
help_text=_('transaction amount'), )
authorization = models.CharField(_('authorization code'),
null=True,
blank=True,
max_length=120,
help_text=_('authorization code for completed transaction'), )
description = models.CharField(_('description'),
max_length=1024,
help_text=_('description of transaction'), )
def __init__(self, *args, **kwargs):
super(LegacyAMSTransaction, self).__init__(*args, **kwargs)
# if the email is provided, then look up a user that matches
if kwargs.get('email'):
user = User.objects.filter(email=kwargs.get('email')).first()
if user:
self.user = user
def __unicode__(self):
return u'%s %s (%s)' % (self.transno, self.email, self.purchase_type, )
@staticmethod
def authorization_from_comment(comment):
"""Grabs the transaction number from the comment from AMS' log"""
regex = re.match(r".*uthorization number (.*?)$", comment)
ret = None
if regex:
try:
ret = regex.group(1)
except Exception:
pass
return ret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment