delint
parent
3084ab837c
commit
cb50e931bd
|
@ -1,7 +1,4 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from datetime import datetime
|
||||
|
||||
from regluit.payment import baseprocessor
|
||||
from regluit.payment.baseprocessor import BasePaymentRequest
|
||||
|
@ -9,28 +6,26 @@ from regluit.payment.parameters import (
|
|||
PAYMENT_HOST_CREDIT,
|
||||
PAYMENT_TYPE_AUTHORIZATION,
|
||||
PAYMENT_TYPE_INSTANT,
|
||||
TRANSACTION_STATUS_ACTIVE,
|
||||
TRANSACTION_STATUS_COMPLETE,
|
||||
TRANSACTION_STATUS_ERROR,
|
||||
TRANSACTION_STATUS_CANCELED,
|
||||
)
|
||||
from regluit.payment.signals import transaction_charged
|
||||
|
||||
def pledge_transaction(t,user,amount):
|
||||
def pledge_transaction(t, user, amount):
|
||||
"""commit <amount> from a <user>'s credit to a specified transaction <t>"""
|
||||
|
||||
|
||||
if t.amount and t.host == PAYMENT_HOST_CREDIT:
|
||||
#changing the pledge_transaction
|
||||
success = user.credit.add_to_pledged(amount-t.amount)
|
||||
else:
|
||||
else:
|
||||
success = user.credit.add_to_pledged(amount)
|
||||
if success:
|
||||
t.type = PAYMENT_TYPE_AUTHORIZATION
|
||||
t.max_amount=amount
|
||||
t.max_amount = amount
|
||||
t.set_credit_approved(amount)
|
||||
return success
|
||||
|
||||
def credit_transaction(t,user,amount):
|
||||
def credit_transaction(t, user, amount):
|
||||
'''user has new credit, use it to fund the transaction'''
|
||||
# first, credit the user's account
|
||||
success = user.credit.add_to_balance(amount)
|
||||
|
@ -44,7 +39,7 @@ def credit_transaction(t,user,amount):
|
|||
|
||||
def pay_transaction(t, user, to_user, amount):
|
||||
'''user has credit, transfer it to rh account'''
|
||||
success = user.credit.transfer_to(to_user , amount)
|
||||
success = user.credit.transfer_to(to_user, amount)
|
||||
if success:
|
||||
t.type = PAYMENT_TYPE_INSTANT
|
||||
t.set_executed()
|
||||
|
@ -53,19 +48,19 @@ def pay_transaction(t, user, to_user, amount):
|
|||
class Processor(baseprocessor.Processor):
|
||||
class CancelPreapproval(BasePaymentRequest):
|
||||
'''
|
||||
Cancels an exisiting token.
|
||||
Cancels an exisiting token.
|
||||
'''
|
||||
|
||||
|
||||
def __init__(self, transaction):
|
||||
self.transaction = transaction
|
||||
if transaction.user.credit.add_to_pledged(-transaction.amount):
|
||||
#success
|
||||
transaction.status=TRANSACTION_STATUS_CANCELED
|
||||
transaction.status = TRANSACTION_STATUS_CANCELED
|
||||
transaction.save()
|
||||
else:
|
||||
self.errorMessage="couldn't cancel the transaction"
|
||||
self.errorMessage = "couldn't cancel the transaction"
|
||||
self.status = 'Credit Cancel Failure'
|
||||
|
||||
|
||||
class PreapprovalDetails(BasePaymentRequest):
|
||||
status = None
|
||||
approved = None
|
||||
|
@ -94,13 +89,8 @@ class Processor(baseprocessor.Processor):
|
|||
user_to_pay = transaction.campaign.user_to_pay
|
||||
credited = user_to_pay.credit.add_to_balance(amount, notify=False)
|
||||
transaction.status = TRANSACTION_STATUS_COMPLETE
|
||||
transaction.date_payment = now()
|
||||
transaction.date_payment = datetime.now()
|
||||
transaction.save()
|
||||
|
||||
|
||||
# fire signal for sucessful transaction
|
||||
transaction_charged.send(sender=self, transaction=transaction)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -323,7 +323,7 @@ class PaymentManager( object ):
|
|||
|
||||
# only allow active transactions to go through again, if there is an error, intervention is needed
|
||||
transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACTION_STATUS_ACTIVE)
|
||||
|
||||
|
||||
results = []
|
||||
|
||||
for t in transactions:
|
||||
|
|
|
@ -1,112 +1,125 @@
|
|||
"""
|
||||
external library imports
|
||||
"""
|
||||
import datetime
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from django.utils.http import urlquote
|
||||
import logging
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from jsonfield import JSONField
|
||||
|
||||
"""
|
||||
django imports
|
||||
"""
|
||||
## django imports
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db.models.signals import post_save, post_delete, pre_save
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.utils.http import urlquote
|
||||
|
||||
## django module imports
|
||||
|
||||
"""
|
||||
django module imports
|
||||
"""
|
||||
from notification import models as notification
|
||||
|
||||
|
||||
"""
|
||||
regluit imports
|
||||
"""
|
||||
from regluit.payment.parameters import *
|
||||
## regluit imports
|
||||
|
||||
from regluit.payment.parameters import (
|
||||
PAYMENT_TYPE_NONE,
|
||||
PAYMENT_TYPE_AUTHORIZATION,
|
||||
|
||||
PAYMENT_HOST_NONE,
|
||||
|
||||
PAYMENT_HOST_CREDIT,
|
||||
|
||||
EXECUTE_TYPE_NONE,
|
||||
TRANSACTION_STATUS_NONE,
|
||||
TRANSACTION_STATUS_ACTIVE,
|
||||
TRANSACTION_STATUS_ERROR,
|
||||
TRANSACTION_STATUS_FAILED,
|
||||
)
|
||||
|
||||
from regluit.payment.signals import credit_balance_added, pledge_created
|
||||
from regluit.utils.localdatetime import now, date_today
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# in fitting stripe -- here are possible fields to fit in with Transaction
|
||||
# c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid,
|
||||
# c.id, c.amount, c.amount_refunded, c.currency, c.description,
|
||||
# datetime.fromtimestamp(c.created, tz=utc), c.paid,
|
||||
# c.fee, c.disputed, c.amount_refunded, c.failure_message,
|
||||
# c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year
|
||||
|
||||
# promising fields
|
||||
|
||||
class Transaction(models.Model):
|
||||
|
||||
|
||||
# type e.g., PAYMENT_TYPE_INSTANT or PAYMENT_TYPE_AUTHORIZATION -- defined in parameters.py
|
||||
type = models.IntegerField(default=PAYMENT_TYPE_NONE, null=False)
|
||||
|
||||
# host: the payment processor. Named after the payment module that hosts the payment processing functions
|
||||
|
||||
# host: the payment processor.
|
||||
#Named after the payment module that hosts the payment processing functions
|
||||
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
|
||||
|
||||
|
||||
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
|
||||
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
|
||||
|
||||
|
||||
# status: general status constants defined in parameters.py
|
||||
status = models.CharField(max_length=32, default=TRANSACTION_STATUS_NONE, null=False)
|
||||
|
||||
|
||||
# local_status: status code specific to the payment processor
|
||||
local_status = models.CharField(max_length=32, default='NONE', null=True)
|
||||
|
||||
|
||||
# amount & currency -- amount of money and its currency involved for transaction
|
||||
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
max_amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
currency = models.CharField(max_length=10, default='USD', null=True)
|
||||
|
||||
|
||||
# a unique ID that can be passed to PayPal to track a transaction
|
||||
secret = models.CharField(max_length=64, null=True)
|
||||
|
||||
|
||||
# a paykey that PayPal generates to identify this transaction
|
||||
pay_key = models.CharField(max_length=128, null=True)
|
||||
|
||||
|
||||
# a preapproval key that Paypal generates to identify this transaction
|
||||
preapproval_key = models.CharField(max_length=128, null=True)
|
||||
|
||||
# (RY is not sure what receipt is for; t4u has hijacked this to be an email address for user.is_anonymous to send a receipt to)
|
||||
|
||||
# (RY is not sure what receipt is for; t4u has hijacked this to be an email address for
|
||||
# user.is_anonymous to send a receipt to)
|
||||
receipt = models.CharField(max_length=256, null=True)
|
||||
|
||||
|
||||
# whether a Preapproval has been approved or not
|
||||
approved = models.NullBooleanField(null=True)
|
||||
|
||||
|
||||
# error message from a transaction
|
||||
error = models.CharField(max_length=256, null=True)
|
||||
|
||||
|
||||
# IPN.reason_code
|
||||
reason = models.CharField(max_length=64, null=True)
|
||||
|
||||
|
||||
# creation and last modified timestamps
|
||||
date_created = models.DateTimeField(auto_now_add=True, db_index=True,)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
# date_payment: when an attempt is made to make the primary payment
|
||||
date_payment = models.DateTimeField(null=True)
|
||||
|
||||
|
||||
# date_executed: when an attempt is made to send money to non-primary chained receivers
|
||||
date_executed = models.DateTimeField(null=True)
|
||||
|
||||
|
||||
# datetime for creation of preapproval and for its expiration
|
||||
date_authorized = models.DateTimeField(null=True)
|
||||
date_expired = models.DateTimeField(null=True)
|
||||
|
||||
|
||||
# associated User, Campaign, and Premium for this Transaction
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
|
||||
campaign = models.ForeignKey('core.Campaign', null=True)
|
||||
premium = models.ForeignKey('core.Premium', null=True)
|
||||
offer = models.ForeignKey('core.Offer', null=True)
|
||||
extra = JSONField(null=True, default={})
|
||||
|
||||
|
||||
# whether the user wants to be not listed publicly
|
||||
anonymous = models.BooleanField(default=False)
|
||||
|
||||
|
@ -121,143 +134,150 @@ class Transaction(models.Model):
|
|||
return 1
|
||||
if self.amount < 100:
|
||||
return 2
|
||||
else:
|
||||
return 3
|
||||
return 3
|
||||
|
||||
@property
|
||||
def deadline_or_now(self):
|
||||
if self.campaign and self.campaign.deadline:
|
||||
return self.campaign.deadline
|
||||
else:
|
||||
return now()
|
||||
|
||||
return now()
|
||||
|
||||
@property
|
||||
def needed_amount(self):
|
||||
if self.user == None or self.user.is_anonymous():
|
||||
if self.user is None or self.user.is_anonymous():
|
||||
return self.max_amount
|
||||
if self.user.credit.available >= self.max_amount:
|
||||
return 0
|
||||
else:
|
||||
return self.max_amount - self.user.credit.available
|
||||
return self.max_amount - self.user.credit.available
|
||||
|
||||
@property
|
||||
def credit_amount(self):
|
||||
if self.user == None or self.user.is_anonymous():
|
||||
if self.user is None or self.user.is_anonymous():
|
||||
return 0
|
||||
if self.user.credit.available >= self.max_amount:
|
||||
return self.max_amount
|
||||
return self.user.credit.available
|
||||
|
||||
|
||||
@property
|
||||
def ack_link(self):
|
||||
return 'https://unglue.it/supporter/%s' % urlquote(self.user.username) if not self.anonymous else ''
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.secret:
|
||||
self.secret = str(uuid.uuid1())
|
||||
super(Transaction, self).save(*args, **kwargs) # Call the "real" save() method.
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return u"-- Transaction:\n \tstatus: %s\n \t amount: %s\n \terror: %s\n" % (self.status, str(self.amount), self.error)
|
||||
|
||||
return u"-- Transaction:\n \tstatus: %s\n \t amount: %s\n \terror: %s\n" % (self.status, str(self.amount), self.error)
|
||||
|
||||
def create_receivers(self, receiver_list):
|
||||
|
||||
|
||||
primary = True
|
||||
for r in receiver_list:
|
||||
receiver = Receiver.objects.create(email=r['email'], amount=r['amount'], currency=self.currency, status="None", primary=primary, transaction=self)
|
||||
receiver = Receiver.objects.create(
|
||||
email=r['email'],
|
||||
amount=r['amount'],
|
||||
currency=self.currency,
|
||||
status="None",
|
||||
primary=primary,
|
||||
transaction=self
|
||||
)
|
||||
primary = False
|
||||
|
||||
|
||||
def get_payment_class(self):
|
||||
'''
|
||||
Returns the specific payment processor that implements this transaction
|
||||
'''
|
||||
if self.host == PAYMENT_HOST_NONE:
|
||||
return None
|
||||
else:
|
||||
mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)])
|
||||
return mod.Processor()
|
||||
|
||||
mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)])
|
||||
return mod.Processor()
|
||||
|
||||
def set_executed(self):
|
||||
self.date_executed = now()
|
||||
self.save()
|
||||
|
||||
def set_payment(self):
|
||||
def set_payment(self):
|
||||
self.date_payment = now()
|
||||
self.save()
|
||||
|
||||
self.save()
|
||||
|
||||
def set_credit_approved(self, amount):
|
||||
self.amount=amount
|
||||
self.amount = amount
|
||||
self.host = PAYMENT_HOST_CREDIT
|
||||
self.type = PAYMENT_TYPE_AUTHORIZATION
|
||||
self.status=TRANSACTION_STATUS_ACTIVE
|
||||
self.approved=True
|
||||
self.status = TRANSACTION_STATUS_ACTIVE
|
||||
self.approved = True
|
||||
now_val = now()
|
||||
self.date_authorized = now_val
|
||||
self.date_expired = now_val + datetime.timedelta( days=settings.PREAPPROVAL_PERIOD )
|
||||
self.date_expired = now_val + datetime.timedelta(days=settings.PREAPPROVAL_PERIOD)
|
||||
self.save()
|
||||
pledge_created.send(sender=self, transaction=self)
|
||||
|
||||
|
||||
def set_pledge_extra(self, pledge_extra):
|
||||
if pledge_extra:
|
||||
self.anonymous = pledge_extra.anonymous
|
||||
self.premium = pledge_extra.premium
|
||||
self.offer = pledge_extra.offer
|
||||
self.extra.update( pledge_extra.extra)
|
||||
self.extra.update(pledge_extra.extra)
|
||||
|
||||
def get_pledge_extra(self):
|
||||
class pe:
|
||||
anonymous=self.anonymous
|
||||
premium=self.premium
|
||||
offer=self.offer
|
||||
extra=self.extra
|
||||
anonymous = self.anonymous
|
||||
premium = self.premium
|
||||
offer = self.offer
|
||||
extra = self.extra
|
||||
return pe
|
||||
|
||||
@classmethod
|
||||
def create(cls, amount=0.00, host=PAYMENT_HOST_NONE, max_amount=0.00, currency='USD',
|
||||
status=TRANSACTION_STATUS_NONE, campaign=None, user=None, pledge_extra=None,
|
||||
donation=False):
|
||||
status=TRANSACTION_STATUS_NONE, campaign=None, user=None, pledge_extra=None,
|
||||
donation=False):
|
||||
if user and user.is_anonymous():
|
||||
user = None
|
||||
t = cls.objects.create(
|
||||
amount=amount,
|
||||
host=host,
|
||||
max_amount=max_amount,
|
||||
currency=currency,
|
||||
status=status,
|
||||
campaign=campaign,
|
||||
user=user,
|
||||
donation=donation,
|
||||
amount=amount,
|
||||
host=host,
|
||||
max_amount=max_amount,
|
||||
currency=currency,
|
||||
status=status,
|
||||
campaign=campaign,
|
||||
user=user,
|
||||
donation=donation,
|
||||
)
|
||||
if pledge_extra:
|
||||
t.set_pledge_extra(pledge_extra)
|
||||
return t
|
||||
|
||||
|
||||
class PaymentResponse(models.Model):
|
||||
# The API used
|
||||
api = models.CharField(max_length=64, null=False)
|
||||
|
||||
|
||||
# The correlation ID
|
||||
correlation_id = models.CharField(max_length=512, null=True)
|
||||
|
||||
|
||||
# the paypal timestamp
|
||||
timestamp = models.CharField(max_length=128, null=True)
|
||||
|
||||
|
||||
# extra info we want to store if an error occurs such as the response message
|
||||
info = models.CharField(max_length=1024, null=True)
|
||||
|
||||
|
||||
# local status specific to the api call
|
||||
status = models.CharField(max_length=32, null=True)
|
||||
|
||||
|
||||
transaction = models.ForeignKey(Transaction, null=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"PaymentResponse -- api: {0} correlation_id: {1} transaction: {2}".format(self.api, self.correlation_id, unicode(self.transaction))
|
||||
|
||||
|
||||
return u"PaymentResponse -- api: {0} correlation_id: {1} transaction: {2}".format(
|
||||
self.api,
|
||||
self.correlation_id,
|
||||
unicode(self.transaction)
|
||||
)
|
||||
|
||||
|
||||
class Receiver(models.Model):
|
||||
|
||||
|
||||
email = models.CharField(max_length=64)
|
||||
|
||||
|
||||
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
currency = models.CharField(max_length=10)
|
||||
|
||||
|
@ -267,224 +287,232 @@ class Receiver(models.Model):
|
|||
primary = models.BooleanField(default=True)
|
||||
txn_id = models.CharField(max_length=64)
|
||||
transaction = models.ForeignKey(Transaction)
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return u"Receiver -- email: {0} status: {1} transaction: {2}".format(self.email, self.status, unicode(self.transaction))
|
||||
return u"Receiver -- email: {0} status: {1} transaction: {2}".format(
|
||||
self.email,
|
||||
self.status,
|
||||
unicode(self.transaction)
|
||||
)
|
||||
|
||||
class CreditLog(models.Model):
|
||||
# a write only record of Unglue.it Credit Transactions
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
|
||||
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
action = models.CharField(max_length=16)
|
||||
# used to record the sent id when action = 'deposit'
|
||||
sent=models.IntegerField(null=True)
|
||||
|
||||
sent = models.IntegerField(null=True)
|
||||
|
||||
class Credit(models.Model):
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='credit')
|
||||
balance = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
pledged = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
last_activity = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self.balance - self.pledged
|
||||
|
||||
|
||||
def add_to_balance(self, num_credits, notify=True):
|
||||
if self.pledged - self.balance > num_credits : # negative to withdraw
|
||||
if self.pledged - self.balance > num_credits: # negative to withdraw
|
||||
return False
|
||||
else:
|
||||
self.balance = self.balance + num_credits
|
||||
self.save()
|
||||
try: # bad things can happen here if you don't return True
|
||||
CreditLog(user = self.user, amount = num_credits, action="add_to_balance").save()
|
||||
CreditLog(user=self.user, amount=num_credits, action="add_to_balance").save()
|
||||
except:
|
||||
logger.exception("failed to log add_to_balance of %s", num_credits)
|
||||
if notify:
|
||||
try:
|
||||
try:
|
||||
credit_balance_added.send(sender=self, amount=num_credits)
|
||||
except:
|
||||
logger.exception("credit_balance_added notification failed of %s", num_credits)
|
||||
return True
|
||||
|
||||
|
||||
def add_to_pledged(self, num_credits):
|
||||
num_credits=Decimal(num_credits)
|
||||
num_credits = Decimal(num_credits)
|
||||
if num_credits is Decimal('NaN'):
|
||||
return False
|
||||
if self.balance - self.pledged < num_credits :
|
||||
if self.balance - self.pledged < num_credits:
|
||||
return False
|
||||
else:
|
||||
self.pledged=self.pledged + num_credits
|
||||
self.save()
|
||||
try: # bad things can happen here if you don't return True
|
||||
CreditLog(user = self.user, amount = num_credits, action="add_to_pledged").save()
|
||||
except:
|
||||
logger.exception("failed to log add_to_pledged of %s", num_credits)
|
||||
return True
|
||||
|
||||
self.pledged = self.pledged + num_credits
|
||||
self.save()
|
||||
try: # bad things can happen here if you don't return True
|
||||
CreditLog(user=self.user, amount=num_credits, action="add_to_pledged").save()
|
||||
except:
|
||||
logger.exception("failed to log add_to_pledged of %s", num_credits)
|
||||
return True
|
||||
|
||||
def use_pledge(self, num_credits):
|
||||
num_credits=Decimal(num_credits)
|
||||
num_credits = Decimal(num_credits)
|
||||
if num_credits is Decimal('NaN'):
|
||||
return False
|
||||
if self.pledged < num_credits :
|
||||
if self.pledged < num_credits:
|
||||
return False
|
||||
self.pledged = self.pledged - num_credits
|
||||
self.balance = self.balance - num_credits
|
||||
self.save()
|
||||
try:
|
||||
CreditLog(user = self.user, amount = - num_credits, action="use_pledge").save()
|
||||
CreditLog(user=self.user, amount=-num_credits, action="use_pledge").save()
|
||||
except:
|
||||
logger.exception("failed to log use_pledge of %s", num_credits)
|
||||
return True
|
||||
|
||||
|
||||
def transfer_to(self, receiver, num_credits, notify=True):
|
||||
num_credits=Decimal(num_credits)
|
||||
if num_credits is Decimal('NaN') or not isinstance( receiver, User):
|
||||
logger.info('fail: %s, %s' % (num_credits,receiver))
|
||||
num_credits = Decimal(num_credits)
|
||||
if num_credits is Decimal('NaN') or not isinstance(receiver, User):
|
||||
logger.info('fail: %s, %s' % (num_credits, receiver))
|
||||
return False
|
||||
if self.add_to_balance(-num_credits):
|
||||
if receiver.credit.add_to_balance(num_credits, notify):
|
||||
return True
|
||||
else:
|
||||
# unwind transfer
|
||||
self.add_to_balance(num_credits, notify)
|
||||
return False
|
||||
else:
|
||||
# unwind transfer
|
||||
self.add_to_balance(num_credits, notify)
|
||||
return False
|
||||
return False
|
||||
|
||||
class Sent(models.Model):
|
||||
'''used by gift view to record gifts it has sent'''
|
||||
user = models.CharField(max_length=32, null=True)
|
||||
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
||||
class Account(models.Model):
|
||||
"""holds references to accounts at third party payment gateways, especially for representing credit cards"""
|
||||
|
||||
# the following fields from stripe Customer might be relevant to Account -- we need to pick good selection
|
||||
# c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc), c.account_balance, c.delinquent,
|
||||
# c.active_card.fingerprint, c.active_card.type, c.active_card.last4, c.active_card.exp_month, c.active_card.exp_year,
|
||||
|
||||
# the following fields from stripe Customer might be relevant to Account
|
||||
# -- we need to pick good selection
|
||||
# c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc),
|
||||
# c.account_balance, c.delinquent,
|
||||
# c.active_card.fingerprint, c.active_card.type, c.active_card.last4,
|
||||
# c.active_card.exp_month, c.active_card.exp_year,
|
||||
# c.active_card.country
|
||||
|
||||
|
||||
# ACTIVE, DEACTIVATED, EXPIRED, EXPIRING, or ERROR
|
||||
STATUS_CHOICES = (
|
||||
('ACTIVE','ACTIVE'),
|
||||
('DEACTIVATED','DEACTIVATED'),
|
||||
('EXPIRED','EXPIRED'),
|
||||
('EXPIRING','EXPIRING'),
|
||||
( 'ERROR','ERROR')
|
||||
)
|
||||
|
||||
# host: the payment processor. Named after the payment module that hosts the payment processing functions
|
||||
STATUS_CHOICES = (
|
||||
('ACTIVE', 'ACTIVE'),
|
||||
('DEACTIVATED', 'DEACTIVATED'),
|
||||
('EXPIRED', 'EXPIRED'),
|
||||
('EXPIRING', 'EXPIRING'),
|
||||
('ERROR', 'ERROR')
|
||||
)
|
||||
|
||||
# host: the payment processor. Named after the payment module that
|
||||
# hosts the payment processing functions
|
||||
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
|
||||
account_id = models.CharField(max_length=128, null=True)
|
||||
|
||||
|
||||
# card related info
|
||||
card_last4 = models.CharField(max_length=4, null=True)
|
||||
|
||||
|
||||
# Visa, American Express, MasterCard, Discover, JCB, Diners Club, or Unknown
|
||||
card_type = models.CharField(max_length=32, null=True)
|
||||
card_exp_month = models.IntegerField(null=True)
|
||||
card_exp_year = models.IntegerField(null=True)
|
||||
card_fingerprint = models.CharField(max_length=32, null=True)
|
||||
card_country = models.CharField(max_length=2, null=True)
|
||||
|
||||
|
||||
# creation and last modified timestamps
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
date_deactivated = models.DateTimeField(null=True)
|
||||
|
||||
|
||||
# associated User if any
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
|
||||
|
||||
|
||||
# status variable
|
||||
status = models.CharField(max_length=11, choices=STATUS_CHOICES, null=False, default='ACTIVE')
|
||||
|
||||
|
||||
def deactivate(self):
|
||||
"""Don't allow more than one active Account of given host to be associated with a given user"""
|
||||
"""Don't allow more than one active Account of given host to
|
||||
be associated with a given user"""
|
||||
self.date_deactivated = now()
|
||||
self.status = 'DEACTIVATED'
|
||||
self.save()
|
||||
|
||||
|
||||
def calculated_status(self):
|
||||
"""returns ACTIVE, DEACTIVATED, EXPIRED, EXPIRING, or ERROR"""
|
||||
|
||||
|
||||
# is it deactivated?
|
||||
|
||||
|
||||
today = date_today()
|
||||
|
||||
|
||||
if self.date_deactivated is not None:
|
||||
return 'DEACTIVATED'
|
||||
|
||||
|
||||
# is it expired?
|
||||
|
||||
elif self.card_exp_year < today.year or (self.card_exp_year == today.year and self.card_exp_month < today.month):
|
||||
|
||||
elif self.card_exp_year < today.year or (
|
||||
self.card_exp_year == today.year and self.card_exp_month < today.month
|
||||
):
|
||||
return 'EXPIRED'
|
||||
|
||||
|
||||
# about to expire? do I want to distinguish from 'ACTIVE'?
|
||||
|
||||
elif (self.card_exp_year == today.year and self.card_exp_month == today.month):
|
||||
return 'EXPIRING'
|
||||
|
||||
|
||||
elif self.card_exp_year == today.year and self.card_exp_month == today.month:
|
||||
return 'EXPIRING'
|
||||
|
||||
# any transactions w/ errors after the account date?
|
||||
# Transaction.objects.filter(host='stripelib', status='Error', approved=True).count()
|
||||
|
||||
elif Transaction.objects.filter(host='stripelib',
|
||||
status='Error', approved=True, user=self.user).filter(date_payment__gt=self.date_created):
|
||||
|
||||
elif Transaction.objects.filter(host='stripelib',
|
||||
status='Error', approved=True, user=self.user
|
||||
).filter(date_payment__gt=self.date_created):
|
||||
return 'ERROR'
|
||||
else:
|
||||
return 'ACTIVE'
|
||||
|
||||
|
||||
def update_status( self, value=None, send_notice_on_change_only=True):
|
||||
"""set Account.status = value unless value is None, in which case,
|
||||
return 'ACTIVE'
|
||||
|
||||
|
||||
def update_status(self, value=None, send_notice_on_change_only=True):
|
||||
"""set Account.status = value unless value is None, in which case,
|
||||
we set Account.status=self.calculated_status()
|
||||
fire off associated notifications
|
||||
|
||||
By default, send notices only if the status is *changing*.
|
||||
|
||||
By default, send notices only if the status is *changing*.
|
||||
Set send_notice_on_change_only = False to
|
||||
send notice based on new_status regardless of old status.
|
||||
send notice based on new_status regardless of old status.
|
||||
(Useful for initialization)
|
||||
"""
|
||||
old_status = self.status
|
||||
|
||||
|
||||
if value is None:
|
||||
new_status = self.calculated_status()
|
||||
else:
|
||||
new_status = value
|
||||
|
||||
|
||||
if new_status == 'EXPIRED':
|
||||
self.deactivate()
|
||||
elif old_status != new_status:
|
||||
self.status = new_status
|
||||
self.save()
|
||||
|
||||
|
||||
# don't notify null users (non-users can buy-to-unglue or thank-for-ungluing)
|
||||
if self.user and (not send_notice_on_change_only or (old_status != new_status)):
|
||||
|
||||
logger.info( "Account status change: %d %s %s", self.pk, old_status, new_status)
|
||||
|
||||
logger.info("Account status change: %d %s %s", self.pk, old_status, new_status)
|
||||
|
||||
if new_status == 'EXPIRING':
|
||||
|
||||
logger.info( "EXPIRING. send to instance.user: %s site: %s", self.user,
|
||||
|
||||
logger.info("EXPIRING. send to instance.user: %s site: %s", self.user,
|
||||
Site.objects.get_current())
|
||||
|
||||
|
||||
# fire off an account_expiring notice -- might not want to do this immediately
|
||||
|
||||
|
||||
notification.queue([self.user], "account_expiring", {
|
||||
'user': self.user,
|
||||
'site':Site.objects.get_current()
|
||||
}, True)
|
||||
|
||||
elif new_status == 'EXPIRED':
|
||||
logger.info( "EXPIRING. send to instance.user: %s site: %s", self.user,
|
||||
logger.info("EXPIRING. send to instance.user: %s site: %s", self.user,
|
||||
Site.objects.get_current())
|
||||
|
||||
|
||||
notification.queue([self.user], "account_expired", {
|
||||
'user': self.user,
|
||||
'site':Site.objects.get_current()
|
||||
}, True)
|
||||
|
||||
|
||||
elif new_status == 'ERROR':
|
||||
# TO DO: what to do?
|
||||
pass
|
||||
|
@ -494,10 +522,13 @@ class Account(models.Model):
|
|||
pass
|
||||
|
||||
def recharge_failed_transactions(self):
|
||||
"""When a new Account is saved, check whether this is the new active account for a user. If so, recharge any
|
||||
outstanding failed transactions
|
||||
"""When a new Account is saved, check whether this is the new active account for a user.
|
||||
If so, recharge any outstanding failed transactions
|
||||
"""
|
||||
transactions_to_recharge = self.user.transaction_set.filter((Q(status=TRANSACTION_STATUS_FAILED) | Q(status=TRANSACTION_STATUS_ERROR)) & Q(campaign__status='SUCCESSFUL')).all()
|
||||
transactions_to_recharge = self.user.transaction_set.filter(
|
||||
(Q(status=TRANSACTION_STATUS_FAILED) | Q(status=TRANSACTION_STATUS_ERROR)) &
|
||||
Q(campaign__status='SUCCESSFUL')
|
||||
).all()
|
||||
|
||||
if transactions_to_recharge:
|
||||
from regluit.payment.manager import PaymentManager
|
||||
|
@ -505,10 +536,13 @@ class Account(models.Model):
|
|||
for transaction in transactions_to_recharge:
|
||||
# check whether we are still within the window to recharge
|
||||
if (now() - transaction.deadline_or_now) < datetime.timedelta(settings.RECHARGE_WINDOW):
|
||||
logger.info("Recharging transaction {0} w/ status {1}".format(transaction.id, transaction.status))
|
||||
logger.info("Recharging transaction {0} w/ status {1}".format(
|
||||
transaction.id,
|
||||
transaction.status
|
||||
))
|
||||
pm.execute_transaction(transaction, [])
|
||||
|
||||
|
||||
|
||||
# handle any save, updates to a payment.Transaction
|
||||
|
||||
def handle_transaction_change(sender, instance, created, **kwargs):
|
||||
|
@ -525,9 +559,7 @@ def handle_transaction_delete(sender, instance, **kwargs):
|
|||
campaign.update_left()
|
||||
return True
|
||||
|
||||
post_save.connect(handle_transaction_change,sender=Transaction)
|
||||
post_delete.connect(handle_transaction_delete,sender=Transaction)
|
||||
post_save.connect(handle_transaction_change, sender=Transaction)
|
||||
post_delete.connect(handle_transaction_delete, sender=Transaction)
|
||||
|
||||
# handle recharging failed transactions
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue