From c3f922e9ba61bc412657cfa18c7d8f8d3df6eb38 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Mon, 14 Jan 2013 16:18:19 -0800 Subject: [PATCH] First pass at regluit.payment.models.Account.status --- expiring_stripe_cc.ipynb | 167 +++++++++++++++++++++++++++++++++++++-- payment/models.py | 40 +++++++++- 2 files changed, 200 insertions(+), 7 deletions(-) diff --git a/expiring_stripe_cc.ipynb b/expiring_stripe_cc.ipynb index 7a22caba..df64285c 100644 --- a/expiring_stripe_cc.ipynb +++ b/expiring_stripe_cc.ipynb @@ -27,6 +27,14 @@ "metadata": {}, "outputs": [] }, + { + "cell_type": "heading", + "level": 2, + "metadata": {}, + "source": [ + "expiring, expired, soon to expire cards" + ] + }, { "cell_type": "code", "collapsed": false, @@ -34,7 +42,9 @@ "from regluit.payment.models import Account\n", "from django.db.models import Q\n", "\n", - "month = 7\n", + "# set the month/year for comparison\n", + "\n", + "month = 1\n", "year = 2013\n", "\n", "# look only at active accounts\n", @@ -52,10 +62,43 @@ "\n", "accounts_expiring_later = active_accounts.filter((Q(card_exp_year__gt=year) | Q(card_exp_year=year, card_exp_month__gt = month)))\n", "\n", - "print active_accounts.count()\n", - "print accounts_expired.count(), accounts_expiring.count(), accounts_expiring_later.count()\n", + "print \"number of active accounts\", active_accounts.count()\n", + "print \"expired: {0} expiring: {1} expire later: {2}\".format(accounts_expired.count(), accounts_expiring.count(), accounts_expiring_later.count())\n", "\n", - "[(account.card_exp_month, account.card_exp_year) for account in accounts_expired]" + "# expiring soon\n", + "print \"expiring soon\"\n", + "print [(account.user, account.card_exp_month, account.card_exp_year) for account in accounts_expiring]\n", + "\n", + "# expired\n", + "print \"expired\"\n", + "print [(account.user, account.card_exp_month, account.card_exp_year) for account in accounts_expired]" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "accounts with problem transactions" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# easy to figure out the card used for a specific problem transaction?\n", + "# want to figure out problem status for a given Account\n", + "\n", + "from regluit.payment.models import Transaction\n", + "\n", + "# Account has fingerprint\n", + "# transaction doesn't have fingerprint -- will have to calculate fingerprint of card associated with transaction\n", + "# w/ error if we store pay_key -- any problem?\n", + "\n", + "Transaction.objects.filter(host='stripelib', status='Error', approved=True).count()" ], "language": "python", "metadata": {}, @@ -65,7 +108,121 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# validity of accounts" + "I am hoping that we can use the API to ask for a list of charge.failed --> but I don't see a way to query charges based upon on the status of the charges -- what you have to iterate through all of the charges and filter based on status. ( maybe I should confirm this fact with people at stripe) -- ok let's do that for now" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from regluit.payment.stripelib import StripeClient\n", + "from regluit.payment.models import Transaction\n", + "\n", + "import json\n", + "from itertools import islice\n", + "\n", + "sc = StripeClient()\n", + "charges = islice(sc._all_objs('Charge'), None)\n", + "\n", + "failed_charges = [(c.amount, c.id, c.failure_message, json.loads(c.description)['t.id']) for c in charges if c.failure_message is not None]\n", + "print failed_charges\n", + "\n", + "# look up corresponding Transactions and flag the ones that have not been properly charged\n", + "\n", + "print [t.status for t in Transaction.objects.filter(id__in = [fc[3] for fc in failed_charges])]\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# Given the specific account I would like to cut the status... need to handle expiration as well as declined charges\n", + "\n", + "from regluit.payment.models import Transaction\n", + "from regluit.payment.models import Account\n", + "from regluit.utils.localdatetime import now, date_today\n", + "\n", + "from itertools import islice\n", + "\n", + "def account_status(account):\n", + "\n", + "# is it deactivated?\n", + "\n", + " today = date_today()\n", + " transactions_w_error_status_older_account = Transaction.objects.filter(host='stripelib', \n", + " status='Error', approved=True, user=account.user)\n", + " \n", + " if account.date_deactivated is not None:\n", + " return 'DEACTIVATED'\n", + "\n", + "# is it expired?\n", + "\n", + " elif account.card_exp_year < today.year or (account.card_exp_year == today.year and account.card_exp_month < today.month):\n", + " return 'EXPIRED'\n", + " \n", + "# about to expire? do I want to distinguish from 'ACTIVE'?\n", + "\n", + " elif (account.card_exp_year == today.year and account.card_exp_month == today.month):\n", + " return 'EXPIRING' \n", + "\n", + "# any transactions w/ errors after the account date?\n", + "# Transaction.objects.filter(host='stripelib', status='Error', approved=True).count()\n", + "\n", + " elif Transaction.objects.filter(host='stripelib', \n", + " status='Error', approved=True, user=account.user).filter(date_payment__gt=account.date_created):\n", + " return 'ERROR'\n", + " else:\n", + " return 'ACTIVE'\n", + " \n", + "# test out with tghe account that is currently erroring out\n", + "\n", + "acc_with_error = Transaction.objects.get(id=773).user.profile.account\n", + "print account_status(acc_with_error)\n", + "print\n", + "\n", + "for account in islice(Account.objects.all(),10):\n", + " print account_status(account)\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "acc_with_error.status" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "acc_with_error = Transaction.objects.get(id=773).user.profile.account\n", + "acc_with_error.user\n", + "\n", + "trans = Transaction.objects.filter(host='stripelib', \n", + " status='Error', approved=True, user=acc_with_error.user)\n", + "\n", + "acc_with_error.date_created, [t.date_payment for t in trans] \n", + "\n", + "print trans.filter(date_payment__gt=acc_with_error.date_created)" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# validity of accounts -- need to use real stripe keys if we want to look at production data" ] }, { diff --git a/payment/models.py b/payment/models.py index 4cc3cf6b..d59acb19 100644 --- a/payment/models.py +++ b/payment/models.py @@ -5,7 +5,7 @@ from django.db.models import Q from regluit.payment.parameters import * from regluit.payment.signals import credit_balance_added, pledge_created -from regluit.utils.localdatetime import now +from regluit.utils.localdatetime import now, date_today from django.db.models.signals import post_save, post_delete @@ -335,7 +335,43 @@ class Account(models.Model): def deactivate(self): """Don't allow more than one active Account of given host to be associated with a given user""" self.date_deactivated = now() - self.save() + self.save() + + @property + def status(self): + """returns ACTIVE, DEACTIVATED, EXPIRED, EXPIRING, or ERROR""" + + # TO DO: integrate this method in to see whether we are using the right range of values + # also, cache this status by having a value in the db in the future. + + # is it deactivated? + + today = date_today() + transactions_w_error_status_older_account = Transaction.objects.filter(host='stripelib', + status='Error', approved=True, user=self.user) + + 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): + 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' + + # 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): + return 'ERROR' + else: + return 'ACTIVE' + # handle any save, updates to a payment.Transaction