Merge branch 'master' into ebook_selling
commit
587323196a
|
@ -112,6 +112,9 @@ def create_notice_types(app, created_models, verbosity, **kwargs):
|
|||
notification.create_notice_type("wishlist_unsuccessful_amazon", _("Campaign shut down"), _("An ungluing campaign that you supported had to be shut down due to an Amazon Payments policy change."))
|
||||
notification.create_notice_type("pledge_donation_credit", _("Donation Credit Balance"), _("You have a donation credit balance"))
|
||||
notification.create_notice_type("new_wisher", _("New wisher"), _("Someone new has wished for a book that you're the rightsholder for"))
|
||||
notification.create_notice_type("account_expiring", _("Credit Card Expiring Soon"), _("Your credit card is about to expire."))
|
||||
notification.create_notice_type("account_expired", _("Credit Card Has Expired"), _("Your credit card has expired."))
|
||||
notification.create_notice_type("account_active", _("Credit Card Number Updated"), _("Payment method updated."), default = 1)
|
||||
|
||||
signals.post_syncdb.connect(create_notice_types, sender=notification)
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ from regluit.core import (
|
|||
)
|
||||
from regluit.core.models import Campaign
|
||||
from regluit.core.signals import deadline_impending
|
||||
|
||||
from regluit.utils.localdatetime import now, date_today
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -94,7 +95,7 @@ def send_mail_task(subject, message, from_email, recipient_list,
|
|||
def update_active_campaign_status():
|
||||
"""update the status of all active campaigns -- presumed to be run at midnight Eastern time"""
|
||||
return [c.update_status(send_notice=True, ignore_deadline_for_success=True, process_transactions=True) for c in Campaign.objects.filter(status='Active') ]
|
||||
|
||||
|
||||
@task
|
||||
def emit_notifications():
|
||||
logger.info('notifications emitting' )
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{% load humanize %}
|
||||
As you requested, we've updated your account with the payment method you provided.
|
||||
|
||||
If you have any questions, we are happy to help. Simply email us at support@gluejar.com.
|
||||
|
||||
{% if user.profile.account %}
|
||||
The current card we have on file:
|
||||
Card type: {{ user.profile.account.card_type }}
|
||||
Number: ************{{ user.profile.account.card_last4 }}
|
||||
Expiration date: {{ user.profile.account.card_exp_month }}/{{ user.profile.account.card_exp_year }}.
|
||||
We use Stripe to keep your information secure.
|
||||
{% endif %}
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "notification/notice_template.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block comments_graphical %}
|
||||
Payment method updated
|
||||
{% endblock %}
|
||||
|
||||
{% block comments_textual %}
|
||||
<p>As you requested, we've updated your account with the payment method you provided.</p>
|
||||
|
||||
<p>If you have any questions, we are happy to help. Simply email us at
|
||||
<a href="mailto:support@gluejar.com">support@gluejar.com</a>.</p>
|
||||
|
||||
{% if user.profile.account %}
|
||||
<p>The current card we have on file:</p>
|
||||
<b>Card type:</b> {{ user.profile.account.card_type }}<br />
|
||||
<b>Number:</b> ************{{ user.profile.account.card_last4 }}<br />
|
||||
<b>Expiration date:</b> {{ user.profile.account.card_exp_month }}/{{ user.profile.account.card_exp_year }}.<br />
|
||||
<p>We use <a href="https://stripe.com/">Stripe</a> to keep your information secure.</p><br />
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
Payment method updated.
|
|
@ -0,0 +1,20 @@
|
|||
{% load humanize %}
|
||||
We want to let you know that your {{ user.profile.account.card_type }} card ending in {{ user.profile.account.card_last4 }} has expired.
|
||||
|
||||
When you receive your new card, simply go to https://{{ site.domain }}{% url manage_account %} to enter your card information. Thank you!
|
||||
|
||||
If you have any questions, we are happy to help. Simply email us at support@gluejar.com.
|
||||
|
||||
{% if user.profile.account %}
|
||||
The current card we have on file:
|
||||
Card type: {{ user.profile.account.card_type }}
|
||||
Number: ************{{ user.profile.account.card_last4 }}
|
||||
Expiration date: {{ user.profile.account.card_exp_month }}/{{ user.profile.account.card_exp_year }}.
|
||||
We use Stripe to keep your information secure.
|
||||
{% endif %}
|
||||
|
||||
|
||||
Thank you for your support.
|
||||
|
||||
The Unglue.it team
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "notification/notice_template.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block comments_graphical %}
|
||||
Your credit card has expired
|
||||
{% endblock %}
|
||||
|
||||
{% block comments_textual %}
|
||||
<p>We want to let you know that your {{ user.profile.account.card_type }} card ending
|
||||
in {{ user.profile.account.card_last4 }} has expired.
|
||||
When you receive your new card, simply go to
|
||||
<a href="https://{{ site.domain }}{% url manage_account %}">Accounts & Pledges</a>
|
||||
to enter your card information. Thank you!</p>
|
||||
|
||||
<p>If you have any questions, we are happy to help. Simply email us at
|
||||
<a href="mailto:support@gluejar.com">support@gluejar.com</a>.</p>
|
||||
|
||||
{% if user.profile.account %}
|
||||
<p>The current card we have on file:</p>
|
||||
<b>Card type:</b> {{ user.profile.account.card_type }}<br />
|
||||
<b>Number:</b> ************{{ user.profile.account.card_last4 }}<br />
|
||||
<b>Expiration date:</b> {{ user.profile.account.card_exp_month }}/{{ user.profile.account.card_exp_year }}.<br />
|
||||
<p>We use <a href="https://stripe.com/">Stripe</a> to keep your information secure.</p><br />
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
Your credit card has expired.
|
|
@ -0,0 +1,14 @@
|
|||
{% load humanize %}
|
||||
We want to give you advance notice that your {{ user.profile.account.card_type }} card ending in {{ user.profile.account.card_last4 }} will expire this month.
|
||||
|
||||
When you receive your new card, simply go to https://{{ site.domain }}{% url manage_account %} to enter your card information. Thank you!
|
||||
|
||||
If you have any questions, we are happy to help. Simply email us at support@gluejar.com.
|
||||
|
||||
{% if user.profile.account %}
|
||||
The current card we have on file:
|
||||
Card type: {{ user.profile.account.card_type }}
|
||||
Number: ************{{ user.profile.account.card_last4 }}
|
||||
Expiration date: {{ user.profile.account.card_exp_month }}/{{ user.profile.account.card_exp_year }}.
|
||||
We use Stripe to keep your information secure.
|
||||
{% endif %}
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "notification/notice_template.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block comments_graphical %}
|
||||
Your credit card is about to expire
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block comments_textual %}
|
||||
<p>We want to give you advance notice that your {{ user.profile.account.card_type }} card ending
|
||||
in {{ user.profile.account.card_last4 }} will expire this month.
|
||||
When you receive your new card, simply go to
|
||||
<a href="https://{{ site.domain }}{% url manage_account %}">Accounts & Pledges</a>
|
||||
to enter your card information. Thank you!</p>
|
||||
|
||||
<p>If you have any questions, we are happy to help. Simply email us at
|
||||
<a href="mailto:support@gluejar.com">support@gluejar.com</a>.</p>
|
||||
|
||||
{% if user.profile.account %}
|
||||
<p>The current card we have on file:</p>
|
||||
<b>Card type:</b> {{ user.profile.account.card_type }}<br />
|
||||
<b>Number:</b> ************{{ user.profile.account.card_last4 }}<br />
|
||||
<b>Expiration date:</b> {{ user.profile.account.card_exp_month }}/{{ user.profile.account.card_exp_year }}.<br />
|
||||
<p>We use <a href="https://stripe.com/">Stripe</a> to keep your information secure.</p><br />
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
Your credit card is about to expire.
|
|
@ -111,6 +111,32 @@ $j(document).ready(function() {
|
|||
<td colspan="2" id="last"><input type="submit" value="{% trans 'Change' %}"></input></td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3>Credit Card Notifications</h3>
|
||||
<table class="notice_settings table table-striped" width="90%">
|
||||
<tr>
|
||||
<th>{% trans "Notify me when..." %}</th>
|
||||
{% for header in notice_settings.column_headers %}
|
||||
<th>{{ header }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for row in notice_settings.rows %}
|
||||
{% if row.notice_type.label|truncatechars:11 == 'account_...' %}
|
||||
<tr class="{% cycle 'row1' 'row2' %}">
|
||||
<td>
|
||||
<span class="notice_type_description">{% trans row.notice_type.description %}</span>
|
||||
</td>
|
||||
{% for cell in row.cells %}
|
||||
<td>
|
||||
<input type="checkbox" name="{{ cell.0 }}" {% if cell.1 %}checked="yes"{% endif %}/>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td colspan="2" id="last"><input type="submit" value="{% trans 'Change' %}"></input></td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3>Rights Holder Notifications</h3>
|
||||
<table class="notice_settings table table-striped" width="90%">
|
||||
<tr>
|
||||
|
@ -159,4 +185,8 @@ $j(document).ready(function() {
|
|||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% for row in notice_settings.rows %}
|
||||
{{row.notice_type.label}}
|
||||
{% endfor %}
|
||||
|
|
|
@ -0,0 +1,683 @@
|
|||
{
|
||||
"metadata": {
|
||||
"name": "expiring_stripe_cc"
|
||||
},
|
||||
"nbformat": 3,
|
||||
"nbformat_minor": 0,
|
||||
"worksheets": [
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# working out logic around expiring credit cards"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from regluit.payment.stripelib import StripeClient\n",
|
||||
"from regluit.payment.models import Account\n",
|
||||
"\n",
|
||||
"from django.db.models import Q, F\n"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"prompt_number": 1
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# use the localdatetime?\n",
|
||||
"\n",
|
||||
"from regluit.utils import localdatetime\n",
|
||||
"localdatetime.date_today()"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "pyout",
|
||||
"prompt_number": 2,
|
||||
"text": [
|
||||
"datetime.date(2013, 8, 5)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 2
|
||||
},
|
||||
{
|
||||
"cell_type": "heading",
|
||||
"level": 2,
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"expiring, expired, soon to expire cards"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from regluit.payment.models import Account\n",
|
||||
"from django.db.models import Q\n",
|
||||
"import datetime\n",
|
||||
"from dateutil.relativedelta import relativedelta\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# set the month/year for comparison\n",
|
||||
"\n",
|
||||
"# # http://stackoverflow.com/a/15155212/7782\n",
|
||||
"today = datetime.date.today()\n",
|
||||
"year = today.year\n",
|
||||
"month = today.month\n",
|
||||
"\n",
|
||||
"date_before_month = today + relativedelta(months=-1)\n",
|
||||
"year_last_month = date_before_month.year\n",
|
||||
"month_last_month = date_before_month.month\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"#year = 2013\n",
|
||||
"#month = 2\n",
|
||||
"\n",
|
||||
"# look only at active accounts\n",
|
||||
"\n",
|
||||
"active_accounts = Account.objects.filter(Q(date_deactivated__isnull=True))\n",
|
||||
"\n",
|
||||
"# calculate expired cards\n",
|
||||
"accounts_expired = active_accounts.filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lt = month)))\n",
|
||||
"\n",
|
||||
"# expiring on a given month\n",
|
||||
"\n",
|
||||
"accounts_expiring = active_accounts.filter(card_exp_year=year, card_exp_month = month)\n",
|
||||
"\n",
|
||||
"# yet to expire\n",
|
||||
"\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 \"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",
|
||||
"# 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": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"stream": "stdout",
|
||||
"text": [
|
||||
"number of active accounts 190\n",
|
||||
"expired: 17 expiring: 5 expire later: 168\n",
|
||||
"expiring soon\n",
|
||||
"[(<User: ALewis>, 8L, 2013L), (<User: CrisGee>, 8L, 2013L), (<User: pbelang>, 8L, 2013L), (<User: edsu>, 8L, 2013L), (<User: LucaF>, 8L, 2013L)]\n",
|
||||
"expired\n",
|
||||
"[(<User: benkeele>, 7L, 2013L), (<User: librarianry>, 2L, 2013L), (<User: aprcunningham>, 7L, 2013L), (<User: ZanRosin>, 7L, 2013L), (<User: rlitwin>, 6L, 2013L), (<User: nblack72649267>, 6L, 2013L), (<User: chjones>, 7L, 2013L), (<User: ranti>, 1L, 2013L), (<User: stellans>, 6L, 2013L), (<User: BillBrowne>, 2L, 2013L), (<User: stigkj>, 2L, 2013L), (<User: AndreaKosavic>, 4L, 2013L), (<User: TerryGammon>, 7L, 2013L), (<User: KateGukeisen>, 7L, 2013L), (<User: DanScott>, 4L, 2013L), (<User: helrond>, 7L, 2013L), (<User: DreamWithMe>, 6L, 2013L)]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 3
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# looking at filtering Accounts that might expire\n",
|
||||
"\n",
|
||||
"# if the the month of the expiration date == next month or earlier \n",
|
||||
"# accounts with expiration of this month and last month -- and to be more expansive filter: expiration with this month or before\n",
|
||||
"# also Account.objects.filter(Q(date_deactivated__isnull=True))\n",
|
||||
"# \n",
|
||||
"# accounts_expired = active_accounts.filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lt = month)))\n",
|
||||
"\n",
|
||||
"accounts_to_consider_expansive = Account.objects.filter(Q(date_deactivated__isnull=True)).filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lte = month)))\n",
|
||||
"\n",
|
||||
"# this month or last last monthj\n",
|
||||
"accounts_to_consider_narrow = Account.objects.filter(Q(date_deactivated__isnull=True)).filter(Q(card_exp_year=year, card_exp_month = month) | Q(card_exp_year=year_last_month, card_exp_month = month_last_month))\n",
|
||||
"\n",
|
||||
"for account in accounts_to_consider_narrow:\n",
|
||||
" print (account.user, account.card_exp_month, account.card_exp_year) "
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"stream": "stdout",
|
||||
"text": [
|
||||
"(<User: benkeele>, 7L, 2013L)\n",
|
||||
"(<User: aprcunningham>, 7L, 2013L)\n",
|
||||
"(<User: ZanRosin>, 7L, 2013L)\n",
|
||||
"(<User: ALewis>, 8L, 2013L)\n",
|
||||
"(<User: CrisGee>, 8L, 2013L)\n",
|
||||
"(<User: chjones>, 7L, 2013L)\n",
|
||||
"(<User: TerryGammon>, 7L, 2013L)\n",
|
||||
"(<User: KateGukeisen>, 7L, 2013L)\n",
|
||||
"(<User: helrond>, 7L, 2013L)\n",
|
||||
"(<User: pbelang>, 8L, 2013L)\n",
|
||||
"(<User: edsu>, 8L, 2013L)\n",
|
||||
"(<User: LucaF>, 8L, 2013L)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 4
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from regluit.utils.localdatetime import date_today\n",
|
||||
"\n",
|
||||
"today = date_today()\n",
|
||||
"year = today.year\n",
|
||||
"month = today.month\n",
|
||||
"accounts_to_calc = Account.objects.filter(Q(date_deactivated__isnull=True)).filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lte = month)))\n",
|
||||
"accounts_to_calc"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "pyout",
|
||||
"prompt_number": 5,
|
||||
"text": [
|
||||
"[<Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, '...(remaining elements truncated)...']"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 5
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from regluit.payment import tasks\n",
|
||||
"k = tasks.update_account_status.apply(args=[False])"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"prompt_number": 6
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"k.get()"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "pyout",
|
||||
"prompt_number": 7,
|
||||
"text": [
|
||||
"[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 7
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# list any active transactions tied to users w/ expiring and expired CC?\n",
|
||||
"\n",
|
||||
"print [(account.user, account.user.email, [t.campaign for t in account.user.transaction_set.filter(status='ACTIVE')]) for account in accounts_expiring]\n",
|
||||
"print [(account.user, account.user.email, [t.campaign for t in account.user.transaction_set.filter(status='ACTIVE')]) for account in accounts_expired]"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"stream": "stdout",
|
||||
"text": [
|
||||
"[(<User: ALewis>, u'alewis4722@verizon.net', []), (<User: CrisGee>, u'bookish37@gmail.com', []), (<User: pbelang>, u'pbelang@uwindsor.ca', []), (<User: edsu>, u'ed.summers@gmail.com', []), (<User: LucaF>, u'lurikfa@gmail.com', [])]\n",
|
||||
"[(<User: benkeele>, u'benjamin.j.keele@gmail.com', []), (<User: librarianry>, u'rclaringbole@gmail.com', []), (<User: aprcunningham>, u'aprcunningham@gmail.com', []), (<User: ZanRosin>, u'zanrosin@gmail.com', []), (<User: rlitwin>, u'rlitwin@gmail.com', []), (<User: nblack72649267>, u'nblack726@aol.com', []), (<User: chjones>, u'chjones@aleph0.com', []), (<User: ranti>, u'ranti.junus@gmail.com', []), (<User: stellans>, u'stellans@gmail.com', []), (<User: BillBrowne>, u'williamgeorgebrowne@gmail.com', []), (<User: stigkj>, u'stigkj@gmail.com', []), (<User: AndreaKosavic>, u'akosavic@gmail.com', []), (<User: TerryGammon>, u'terry.gammon@gmail.com', []), (<User: KateGukeisen>, u'kegukeis@syr.edu', []), (<User: DanScott>, u'dan@coffeecode.net', []), (<User: helrond>, u'hillel.arnold@gmail.com', []), (<User: DreamWithMe>, u'astanton@booklamp.org', [])]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 6
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# more to the point, what cards have expired or will expire by the time we have a hopefully \n",
|
||||
"# successful close for Feeding the City (campaign # 15)?\n",
|
||||
"\n",
|
||||
"from django.contrib.auth.models import User\n",
|
||||
"from regluit.core.models import Campaign\n",
|
||||
"\n",
|
||||
"ftc_campaign = Campaign.objects.get(id=15)\n",
|
||||
"\n",
|
||||
"# get all accounts tied to this campaign....\n",
|
||||
"len(ftc_campaign.supporters())\n",
|
||||
"\n",
|
||||
"ftc_expired_accounts = [User.objects.get(id=supporter_id).profile.account for supporter_id in ftc_campaign.supporters()\n",
|
||||
" if User.objects.get(id=supporter_id).profile.account.status == 'EXPIRED' or \n",
|
||||
" User.objects.get(id=supporter_id).profile.account.status == 'EXPIRING']\n",
|
||||
"ftc_expired_accounts\n"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "pyout",
|
||||
"prompt_number": 7,
|
||||
"text": [
|
||||
"[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 7
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"Account.objects.filter(status='EXPIRED')"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "pyout",
|
||||
"prompt_number": 8,
|
||||
"text": [
|
||||
"[<Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>, <Account: Account object>]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 8
|
||||
},
|
||||
{
|
||||
"cell_type": "heading",
|
||||
"level": 1,
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"coming up with good notices to send out "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from notification.engine import send_all\n",
|
||||
"from notification import models as notification\n",
|
||||
"\n",
|
||||
"from django.contrib.sites.models import Site"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"prompt_number": 9
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from django.contrib.auth.models import User\n",
|
||||
"from django.conf import settings\n",
|
||||
"me = User.objects.get(email = settings.EMAIL_HOST_USER )"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"prompt_number": 10
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"print me, settings.EMAIL_HOST"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"stream": "stdout",
|
||||
"text": [
|
||||
"eric smtp.gmail.com\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"prompt_number": 11
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"notification.send_now([me], \"account_expiring\", {\n",
|
||||
" 'user': me, \n",
|
||||
" 'site':Site.objects.get_current()\n",
|
||||
" }, True)"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"prompt_number": 12
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"notification.send_now([me], \"account_expired\", {\n",
|
||||
" 'user': me, \n",
|
||||
" 'site':Site.objects.get_current()\n",
|
||||
" }, True)"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"prompt_number": 14
|
||||
},
|
||||
{
|
||||
"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": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"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.\n",
|
||||
"\n",
|
||||
"**Note: One needs to have productionstripe keys loaded in database to run following code**\n",
|
||||
"\n",
|
||||
"What script do I run to load these keys? \n",
|
||||
"\n",
|
||||
"`/Volumes/ryvault1/gluejar/stripe/set_stripe_keys.py`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "heading",
|
||||
"level": 2,
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Work to create an Account.account_status()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First working out conditions for **ERROR** status"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# acc_with_error = transaction # 773 -- the one with an Error that we wrote off\n",
|
||||
"\n",
|
||||
"acc_with_error = Transaction.objects.get(id=773).user.profile.account\n",
|
||||
"acc_with_error.user\n",
|
||||
"\n",
|
||||
"# trans is all stripe transactions of user associated w/ acc_with_error\n",
|
||||
"\n",
|
||||
"trans = Transaction.objects.filter(host='stripelib', \n",
|
||||
" status='Error', approved=True, user=acc_with_error.user)\n",
|
||||
"\n",
|
||||
"# comparing the transaction payment date with when account created.\n",
|
||||
"# why? \n",
|
||||
"# if account created after transaction payment date, we would want to retry the payment.\n",
|
||||
"\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": [
|
||||
"https://github.com/Gluejar/regluit/commit/c3f922e9ba61bc412657cfa18c7d8f8d3df6eb38#L1R341 --> it's made its way into the unglue.it code, at least in the `expiring_cc` branch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
" "
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# test out with the 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",
|
||||
"acc_with_error.status"
|
||||
],
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from regluit.payment.stripelib import StripeClient\n",
|
||||
"from django.db.models import Q\n",
|
||||
"\n",
|
||||
"sc = StripeClient()\n",
|
||||
"customers = list(sc._all_objs('Customer'))\n",
|
||||
"\n",
|
||||
"# 3 checks available to us: Address Line 1, zip code, CVC\n",
|
||||
"\n",
|
||||
"[(customer.id, customer.description, customer.active_card.get(\"address_line1_check\"), \n",
|
||||
"customer.active_card.get(\"address_zip_check\"), \n",
|
||||
"customer.active_card.get(\"cvc_check\")) for customer in customers if customer.active_card is not None]\n"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# look at only customers that are attached to active Account"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"from regluit.payment.stripelib import StripeClient\n",
|
||||
"from regluit.payment.models import Account\n",
|
||||
"\n",
|
||||
"sc = StripeClient()\n",
|
||||
"customers = sc._all_objs('Customer')\n",
|
||||
"\n",
|
||||
"active_accounts = Account.objects.filter(Q(date_deactivated__isnull=True))\n",
|
||||
"\n",
|
||||
"active_customer_ids = set([account.account_id for account in active_accounts])\n"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"[(customer.active_card[\"address_line1_check\"], \n",
|
||||
"customer.active_card[\"address_zip_check\"], \n",
|
||||
"customer.active_card[\"cvc_check\"]) for customer in customers if customer.id in active_customer_ids]"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# handling campaign totals properly based on account statuses\n",
|
||||
"\n",
|
||||
"**Will we need to start marking accounts as expired explicitly?** \n",
|
||||
"\n",
|
||||
"add a manager method?\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# calculate which active transactions not tied to an active account w/ unexpired CC\n"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# should we delete stripe accounts associated with deactivated accounts -- I think yes\n",
|
||||
"\n",
|
||||
"How to do? \n",
|
||||
"\n",
|
||||
"* clean up Customer associated with current deactivated accounts\n",
|
||||
"* build logic in to delete Customer once the correspending account are deactivated and we safely have a new Account/Customer in place -- maybe a good task to put into the webhook handler"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"len(active_customer_ids)"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"collapsed": false,
|
||||
"input": [
|
||||
"# do the users w/ deactivated accounts have active ones?"
|
||||
],
|
||||
"language": "python",
|
||||
"metadata": {},
|
||||
"outputs": []
|
||||
}
|
||||
],
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,372 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# <nbformat>3.0</nbformat>
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# # working out logic around expiring credit cards
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.payment.stripelib import StripeClient
|
||||
from regluit.payment.models import Account
|
||||
|
||||
from django.db.models import Q, F
|
||||
|
||||
# <codecell>
|
||||
|
||||
# use the localdatetime?
|
||||
|
||||
from regluit.utils import localdatetime
|
||||
localdatetime.date_today()
|
||||
|
||||
# <headingcell level=2>
|
||||
|
||||
# expiring, expired, soon to expire cards
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.payment.models import Account
|
||||
from django.db.models import Q
|
||||
import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
# set the month/year for comparison
|
||||
|
||||
# # http://stackoverflow.com/a/15155212/7782
|
||||
today = datetime.date.today()
|
||||
year = today.year
|
||||
month = today.month
|
||||
|
||||
date_before_month = today + relativedelta(months=-1)
|
||||
year_last_month = date_before_month.year
|
||||
month_last_month = date_before_month.month
|
||||
|
||||
|
||||
#year = 2013
|
||||
#month = 2
|
||||
|
||||
# look only at active accounts
|
||||
|
||||
active_accounts = Account.objects.filter(Q(date_deactivated__isnull=True))
|
||||
|
||||
# calculate expired cards
|
||||
accounts_expired = active_accounts.filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lt = month)))
|
||||
|
||||
# expiring on a given month
|
||||
|
||||
accounts_expiring = active_accounts.filter(card_exp_year=year, card_exp_month = month)
|
||||
|
||||
# yet to expire
|
||||
|
||||
accounts_expiring_later = active_accounts.filter((Q(card_exp_year__gt=year) | Q(card_exp_year=year, card_exp_month__gt = month)))
|
||||
|
||||
print "number of active accounts", active_accounts.count()
|
||||
print "expired: {0} expiring: {1} expire later: {2}".format(accounts_expired.count(), accounts_expiring.count(), accounts_expiring_later.count())
|
||||
|
||||
# expiring soon
|
||||
print "expiring soon"
|
||||
print [(account.user, account.card_exp_month, account.card_exp_year) for account in accounts_expiring]
|
||||
|
||||
# expired
|
||||
print "expired"
|
||||
print [(account.user, account.card_exp_month, account.card_exp_year) for account in accounts_expired]
|
||||
|
||||
# <codecell>
|
||||
|
||||
# looking at filtering Accounts that might expire
|
||||
|
||||
# if the the month of the expiration date == next month or earlier
|
||||
# accounts with expiration of this month and last month -- and to be more expansive filter: expiration with this month or before
|
||||
# also Account.objects.filter(Q(date_deactivated__isnull=True))
|
||||
#
|
||||
# accounts_expired = active_accounts.filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lt = month)))
|
||||
|
||||
accounts_to_consider_expansive = Account.objects.filter(Q(date_deactivated__isnull=True)).filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lte = month)))
|
||||
|
||||
# this month or last last monthj
|
||||
accounts_to_consider_narrow = Account.objects.filter(Q(date_deactivated__isnull=True)).filter(Q(card_exp_year=year, card_exp_month = month) | Q(card_exp_year=year_last_month, card_exp_month = month_last_month))
|
||||
|
||||
for account in accounts_to_consider_narrow:
|
||||
print (account.user, account.card_exp_month, account.card_exp_year)
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.utils.localdatetime import date_today
|
||||
|
||||
today = date_today()
|
||||
year = today.year
|
||||
month = today.month
|
||||
accounts_to_calc = Account.objects.filter(Q(date_deactivated__isnull=True)).filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lte = month)))
|
||||
accounts_to_calc
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.payment import tasks
|
||||
k = tasks.update_account_status.apply(args=[False])
|
||||
|
||||
# <codecell>
|
||||
|
||||
k.get()
|
||||
|
||||
# <codecell>
|
||||
|
||||
# list any active transactions tied to users w/ expiring and expired CC?
|
||||
|
||||
print [(account.user, account.user.email, [t.campaign for t in account.user.transaction_set.filter(status='ACTIVE')]) for account in accounts_expiring]
|
||||
print [(account.user, account.user.email, [t.campaign for t in account.user.transaction_set.filter(status='ACTIVE')]) for account in accounts_expired]
|
||||
|
||||
# <codecell>
|
||||
|
||||
# more to the point, what cards have expired or will expire by the time we have a hopefully
|
||||
# successful close for Feeding the City (campaign # 15)?
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from regluit.core.models import Campaign
|
||||
|
||||
ftc_campaign = Campaign.objects.get(id=15)
|
||||
|
||||
# get all accounts tied to this campaign....
|
||||
len(ftc_campaign.supporters())
|
||||
|
||||
ftc_expired_accounts = [User.objects.get(id=supporter_id).profile.account for supporter_id in ftc_campaign.supporters()
|
||||
if User.objects.get(id=supporter_id).profile.account.status == 'EXPIRED' or
|
||||
User.objects.get(id=supporter_id).profile.account.status == 'EXPIRING']
|
||||
ftc_expired_accounts
|
||||
|
||||
# <codecell>
|
||||
|
||||
Account.objects.filter(status='EXPIRED')
|
||||
|
||||
# <headingcell level=1>
|
||||
|
||||
# coming up with good notices to send out
|
||||
|
||||
# <codecell>
|
||||
|
||||
from notification.engine import send_all
|
||||
from notification import models as notification
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
# <codecell>
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
me = User.objects.get(email = settings.EMAIL_HOST_USER )
|
||||
|
||||
# <codecell>
|
||||
|
||||
print me, settings.EMAIL_HOST
|
||||
|
||||
# <codecell>
|
||||
|
||||
notification.send_now([me], "account_expiring", {
|
||||
'user': me,
|
||||
'site':Site.objects.get_current()
|
||||
}, True)
|
||||
|
||||
# <codecell>
|
||||
|
||||
notification.send_now([me], "account_expired", {
|
||||
'user': me,
|
||||
'site':Site.objects.get_current()
|
||||
}, True)
|
||||
|
||||
# <headingcell level=1>
|
||||
|
||||
# accounts with problem transactions
|
||||
|
||||
# <codecell>
|
||||
|
||||
# easy to figure out the card used for a specific problem transaction?
|
||||
# want to figure out problem status for a given Account
|
||||
|
||||
from regluit.payment.models import Transaction
|
||||
|
||||
# Account has fingerprint
|
||||
# transaction doesn't have fingerprint -- will have to calculate fingerprint of card associated with transaction
|
||||
# w/ error if we store pay_key -- any problem?
|
||||
|
||||
Transaction.objects.filter(host='stripelib', status='Error', approved=True).count()
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# 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.
|
||||
#
|
||||
# **Note: One needs to have productionstripe keys loaded in database to run following code**
|
||||
#
|
||||
# What script do I run to load these keys?
|
||||
#
|
||||
# `/Volumes/ryvault1/gluejar/stripe/set_stripe_keys.py`
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.payment.stripelib import StripeClient
|
||||
from regluit.payment.models import Transaction
|
||||
|
||||
import json
|
||||
from itertools import islice
|
||||
|
||||
sc = StripeClient()
|
||||
charges = islice(sc._all_objs('Charge'), None)
|
||||
|
||||
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]
|
||||
print failed_charges
|
||||
|
||||
# look up corresponding Transactions and flag the ones that have not been properly charged
|
||||
|
||||
print [t.status for t in Transaction.objects.filter(id__in = [fc[3] for fc in failed_charges])]
|
||||
|
||||
# <headingcell level=2>
|
||||
|
||||
# Work to create an Account.account_status()
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# First working out conditions for **ERROR** status
|
||||
|
||||
# <codecell>
|
||||
|
||||
# acc_with_error = transaction # 773 -- the one with an Error that we wrote off
|
||||
|
||||
acc_with_error = Transaction.objects.get(id=773).user.profile.account
|
||||
acc_with_error.user
|
||||
|
||||
# trans is all stripe transactions of user associated w/ acc_with_error
|
||||
|
||||
trans = Transaction.objects.filter(host='stripelib',
|
||||
status='Error', approved=True, user=acc_with_error.user)
|
||||
|
||||
# comparing the transaction payment date with when account created.
|
||||
# why?
|
||||
# if account created after transaction payment date, we would want to retry the payment.
|
||||
|
||||
|
||||
acc_with_error.date_created, [t.date_payment for t in trans]
|
||||
|
||||
print trans.filter(date_payment__gt=acc_with_error.date_created)
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# https://github.com/Gluejar/regluit/commit/c3f922e9ba61bc412657cfa18c7d8f8d3df6eb38#L1R341 --> it's made its way into the unglue.it code, at least in the `expiring_cc` branch
|
||||
|
||||
# <codecell>
|
||||
|
||||
# Given the specific account I would like to cut the status... need to handle expiration as well as declined charges
|
||||
|
||||
from regluit.payment.models import Transaction
|
||||
from regluit.payment.models import Account
|
||||
from regluit.utils.localdatetime import now, date_today
|
||||
|
||||
from itertools import islice
|
||||
|
||||
def account_status(account):
|
||||
|
||||
# is it deactivated?
|
||||
|
||||
today = date_today()
|
||||
transactions_w_error_status_older_account = Transaction.objects.filter(host='stripelib',
|
||||
status='Error', approved=True, user=account.user)
|
||||
|
||||
if account.date_deactivated is not None:
|
||||
return 'DEACTIVATED'
|
||||
|
||||
# is it expired?
|
||||
|
||||
elif account.card_exp_year < today.year or (account.card_exp_year == today.year and account.card_exp_month < today.month):
|
||||
return 'EXPIRED'
|
||||
|
||||
# about to expire? do I want to distinguish from 'ACTIVE'?
|
||||
|
||||
elif (account.card_exp_year == today.year and account.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=account.user).filter(date_payment__gt=account.date_created):
|
||||
return 'ERROR'
|
||||
else:
|
||||
return 'ACTIVE'
|
||||
|
||||
|
||||
# <codecell>
|
||||
|
||||
# test out with the account that is currently erroring out
|
||||
|
||||
acc_with_error = Transaction.objects.get(id=773).user.profile.account
|
||||
print account_status(acc_with_error)
|
||||
print
|
||||
acc_with_error.status
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# # validity of accounts -- need to use real stripe keys if we want to look at production data
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.payment.stripelib import StripeClient
|
||||
from django.db.models import Q
|
||||
|
||||
sc = StripeClient()
|
||||
customers = list(sc._all_objs('Customer'))
|
||||
|
||||
# 3 checks available to us: Address Line 1, zip code, CVC
|
||||
|
||||
[(customer.id, customer.description, customer.active_card.get("address_line1_check"),
|
||||
customer.active_card.get("address_zip_check"),
|
||||
customer.active_card.get("cvc_check")) for customer in customers if customer.active_card is not None]
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# # look at only customers that are attached to active Account
|
||||
|
||||
# <codecell>
|
||||
|
||||
from regluit.payment.stripelib import StripeClient
|
||||
from regluit.payment.models import Account
|
||||
|
||||
sc = StripeClient()
|
||||
customers = sc._all_objs('Customer')
|
||||
|
||||
active_accounts = Account.objects.filter(Q(date_deactivated__isnull=True))
|
||||
|
||||
active_customer_ids = set([account.account_id for account in active_accounts])
|
||||
|
||||
# <codecell>
|
||||
|
||||
[(customer.active_card["address_line1_check"],
|
||||
customer.active_card["address_zip_check"],
|
||||
customer.active_card["cvc_check"]) for customer in customers if customer.id in active_customer_ids]
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# # handling campaign totals properly based on account statuses
|
||||
#
|
||||
# **Will we need to start marking accounts as expired explicitly?**
|
||||
#
|
||||
# add a manager method?
|
||||
|
||||
# <codecell>
|
||||
|
||||
# calculate which active transactions not tied to an active account w/ unexpired CC
|
||||
|
||||
# <markdowncell>
|
||||
|
||||
# # should we delete stripe accounts associated with deactivated accounts -- I think yes
|
||||
#
|
||||
# How to do?
|
||||
#
|
||||
# * clean up Customer associated with current deactivated accounts
|
||||
# * build logic in to delete Customer once the correspending account are deactivated and we safely have a new Account/Customer in place -- maybe a good task to put into the webhook handler
|
||||
|
||||
# <codecell>
|
||||
|
||||
len(active_customer_ids)
|
||||
|
||||
# <codecell>
|
||||
|
||||
# do the users w/ deactivated accounts have active ones?
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'Account.status'
|
||||
db.add_column('payment_account', 'status',
|
||||
self.gf('django.db.models.fields.CharField')(default='INITIALIZED', max_length=16),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Account.status'
|
||||
db.delete_column('payment_account', 'status')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.campaign': {
|
||||
'Meta': {'object_name': 'Campaign'},
|
||||
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
|
||||
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
|
||||
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
|
||||
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
|
||||
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.edition': {
|
||||
'Meta': {'object_name': 'Edition'},
|
||||
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.premium': {
|
||||
'Meta': {'object_name': 'Premium'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
|
||||
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
|
||||
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
|
||||
},
|
||||
'payment.account': {
|
||||
'Meta': {'object_name': 'Account'},
|
||||
'account_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'card_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}),
|
||||
'card_exp_month': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'card_exp_year': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'card_fingerprint': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'card_last4': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True'}),
|
||||
'card_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'date_deactivated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.credit': {
|
||||
'Meta': {'object_name': 'Credit'},
|
||||
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'payment.creditlog': {
|
||||
'Meta': {'object_name': 'CreditLog'},
|
||||
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.paymentresponse': {
|
||||
'Meta': {'object_name': 'PaymentResponse'},
|
||||
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
|
||||
},
|
||||
'payment.receiver': {
|
||||
'Meta': {'object_name': 'Receiver'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
|
||||
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
|
||||
},
|
||||
'payment.sent': {
|
||||
'Meta': {'object_name': 'Sent'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
|
||||
},
|
||||
'payment.transaction': {
|
||||
'Meta': {'object_name': 'Transaction'},
|
||||
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
|
||||
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
|
||||
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
|
||||
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
|
||||
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['payment']
|
|
@ -0,0 +1,237 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
def calculated_status(orm, account):
|
||||
"""returns ACTIVE, DEACTIVATED, EXPIRED, EXPIRING, or ERROR"""
|
||||
|
||||
# is it deactivated?
|
||||
|
||||
from regluit.utils.localdatetime import date_today
|
||||
|
||||
today = date_today()
|
||||
|
||||
if account.date_deactivated is not None:
|
||||
return 'DEACTIVATED'
|
||||
|
||||
# is it expired?
|
||||
|
||||
elif account.card_exp_year < today.year or (account.card_exp_year == today.year and account.card_exp_month < today.month):
|
||||
return 'EXPIRED'
|
||||
|
||||
# about to expire? do I want to distinguish from 'ACTIVE'?
|
||||
|
||||
elif (account.card_exp_year == today.year and account.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 orm.Transaction.objects.filter(host='stripelib',
|
||||
status='Error', approved=True, user=account.user).filter(date_payment__gt=account.date_created):
|
||||
return 'ERROR'
|
||||
else:
|
||||
return 'ACTIVE'
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
for account in orm.Account.objects.all():
|
||||
account.status = calculated_status(orm, account)
|
||||
account.save()
|
||||
|
||||
def backwards(self, orm):
|
||||
for account in orm.Account.objects.all():
|
||||
account.status = "ACTIVE"
|
||||
account.save()
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.campaign': {
|
||||
'Meta': {'object_name': 'Campaign'},
|
||||
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
|
||||
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
|
||||
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
|
||||
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
|
||||
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.edition': {
|
||||
'Meta': {'object_name': 'Edition'},
|
||||
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.premium': {
|
||||
'Meta': {'object_name': 'Premium'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
|
||||
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
|
||||
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
|
||||
},
|
||||
'payment.account': {
|
||||
'Meta': {'object_name': 'Account'},
|
||||
'account_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'card_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}),
|
||||
'card_exp_month': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'card_exp_year': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'card_fingerprint': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'card_last4': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True'}),
|
||||
'card_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'date_deactivated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.credit': {
|
||||
'Meta': {'object_name': 'Credit'},
|
||||
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'payment.creditlog': {
|
||||
'Meta': {'object_name': 'CreditLog'},
|
||||
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.paymentresponse': {
|
||||
'Meta': {'object_name': 'PaymentResponse'},
|
||||
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
|
||||
},
|
||||
'payment.receiver': {
|
||||
'Meta': {'object_name': 'Receiver'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
|
||||
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
|
||||
},
|
||||
'payment.sent': {
|
||||
'Meta': {'object_name': 'Sent'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
|
||||
},
|
||||
'payment.transaction': {
|
||||
'Meta': {'object_name': 'Transaction'},
|
||||
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
|
||||
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
|
||||
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
|
||||
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
|
||||
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['payment']
|
||||
symmetrical = True
|
|
@ -15,14 +15,21 @@ 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.db.models.signals import post_save, post_delete
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db.models.signals import post_save, post_delete, pre_save
|
||||
|
||||
"""
|
||||
django module imports
|
||||
"""
|
||||
from notification import models as notification
|
||||
|
||||
|
||||
"""
|
||||
regluit imports
|
||||
"""
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -320,6 +327,15 @@ class Account(models.Model):
|
|||
# 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
|
||||
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
|
||||
account_id = models.CharField(max_length=128, null=True)
|
||||
|
@ -342,10 +358,110 @@ class Account(models.Model):
|
|||
# associated User if any
|
||||
user = models.ForeignKey(User, 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"""
|
||||
self.date_deactivated = now()
|
||||
self.save()
|
||||
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):
|
||||
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'
|
||||
|
||||
|
||||
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*. Set send_notice_on_change_only = False to
|
||||
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
|
||||
|
||||
self.status = new_status
|
||||
self.save()
|
||||
|
||||
if 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)
|
||||
|
||||
if new_status == 'EXPIRING':
|
||||
|
||||
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,
|
||||
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: we need to figure out notice needs to be sent out if we get an ERROR status.
|
||||
pass
|
||||
|
||||
elif new_status == 'DEACTIVATED':
|
||||
# nothing needs to happen here
|
||||
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
|
||||
"""
|
||||
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
|
||||
pm = PaymentManager()
|
||||
for transaction in transactions_to_recharge:
|
||||
# check whether we are still within the window to recharge
|
||||
if (now() - transaction.campaign.deadline) < datetime.timedelta(settings.RECHARGE_WINDOW):
|
||||
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
|
||||
|
||||
|
@ -368,28 +484,4 @@ post_delete.connect(handle_transaction_delete,sender=Transaction)
|
|||
|
||||
# handle recharging failed transactions
|
||||
|
||||
def recharge_failed_transactions(sender, created, instance, **kwargs):
|
||||
"""When a new Account is saved, check whether this is the new active account for a user. If so, recharge any
|
||||
outstanding failed transactions
|
||||
"""
|
||||
|
||||
# make sure the new account is active
|
||||
if instance.date_deactivated is not None:
|
||||
return False
|
||||
|
||||
transactions_to_recharge = instance.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
|
||||
pm = PaymentManager()
|
||||
for transaction in transactions_to_recharge:
|
||||
# check whether we are still within the window to recharge
|
||||
if (now() - transaction.campaign.deadline) < datetime.timedelta(settings.RECHARGE_WINDOW):
|
||||
logger.info("Recharging transaction {0} w/ status {1}".format(transaction.id, transaction.status))
|
||||
pm.execute_transaction(transaction, [])
|
||||
|
||||
post_save.connect(recharge_failed_transactions, sender=Account)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -590,7 +590,10 @@ class Processor(baseprocessor.Processor):
|
|||
)
|
||||
if user.profile.account:
|
||||
user.profile.account.deactivate()
|
||||
account.save()
|
||||
account.save()
|
||||
account.recharge_failed_transactions()
|
||||
else:
|
||||
account.save()
|
||||
return account
|
||||
|
||||
class Preapproval(StripePaymentRequest, baseprocessor.Processor.Preapproval):
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
"""
|
||||
external library imports
|
||||
"""
|
||||
from celery.task import task
|
||||
|
||||
"""
|
||||
django imports
|
||||
"""
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db.models import Q
|
||||
|
||||
from notification import models as notification
|
||||
|
||||
"""
|
||||
regluit imports
|
||||
"""
|
||||
from regluit.payment.models import Account
|
||||
from regluit.utils.localdatetime import date_today
|
||||
|
||||
#task to update the status of accounts
|
||||
@task
|
||||
def update_account_status(all_accounts=True, send_notice_on_change_only=True):
|
||||
"""update the status of all Accounts
|
||||
|
||||
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. (Useful for initialization)
|
||||
"""
|
||||
errors = []
|
||||
|
||||
if all_accounts:
|
||||
accounts_to_calc = Account.objects.all()
|
||||
else:
|
||||
# active accounts with expiration dates from this month earlier
|
||||
today = date_today()
|
||||
year = today.year
|
||||
month = today.month
|
||||
accounts_to_calc = Account.objects.filter(Q(date_deactivated__isnull=True)).filter((Q(card_exp_year__lt=year) | Q(card_exp_year=year, card_exp_month__lte = month)))
|
||||
|
||||
for account in accounts_to_calc:
|
||||
try:
|
||||
account.update_status(send_notice_on_change_only=send_notice_on_change_only)
|
||||
except Exception, e:
|
||||
errors.append(e)
|
||||
|
||||
# fire off notices
|
||||
|
||||
from regluit.core.tasks import emit_notifications
|
||||
emit_notifications.delay()
|
||||
|
||||
return errors
|
||||
|
||||
# task run roughly 8 days ahead of card expirations
|
||||
@task
|
||||
def notify_expiring_accounts():
|
||||
expiring_accounts = Account.objects.filter(status='EXPIRING')
|
||||
for account in expiring_accounts:
|
||||
notification.send_now([account.user], "account_expiring", {
|
||||
'user': account.user,
|
||||
'site':Site.objects.get_current()
|
||||
}, True)
|
||||
|
||||
# used for bootstrapping our expired cc notification for first time
|
||||
@task
|
||||
def notify_expired_accounts():
|
||||
expired_accounts = Account.objects.filter(status='EXPIRED')
|
||||
for account in expired_accounts:
|
||||
notification.send_now([account.user], "account_expired", {
|
||||
'user': account.user,
|
||||
'site':Site.objects.get_current()
|
||||
}, True)
|
||||
|
|
@ -366,48 +366,47 @@ class BasicGuiTest(TestCase):
|
|||
self.assertFalse(sel.find_elements_by_css_selector('div#user-block-hide')[0].is_displayed())
|
||||
def tearDown(self):
|
||||
self.selenium.quit()
|
||||
|
||||
|
||||
|
||||
class AccountTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# create a user
|
||||
self.user1 = User.objects.create_user('account_test1', 'account_test1@example.org', 'account_test1_pw')
|
||||
self.user1.save()
|
||||
self.user2 = User.objects.create_user('account_test2', 'account_test2@example.org', 'account_test2_pw')
|
||||
self.user2.save()
|
||||
self.account1 = Account(host='host1', account_id='1', user=self.user1)
|
||||
self.account1.save()
|
||||
|
||||
self.account2 = Account(host='host1', account_id='2', user=self.user1)
|
||||
|
||||
def test_constraint_at_most_one_active_account_per_user(self):
|
||||
# https://www.pivotaltracker.com/story/show/37458303
|
||||
|
||||
# hould not be allowed to save account2, which has same host and user
|
||||
self.assertRaises(Exception, self.account2.save)
|
||||
|
||||
# only 1 account left in total
|
||||
self.assertEqual(Account.objects.count(),1)
|
||||
|
||||
def test_payment_manager_retrieve_account(self):
|
||||
pm = PaymentManager()
|
||||
# test whether the retrieval process correct -- should add one for deactivated accounts
|
||||
accts = pm.retrieve_accounts(host='host1', user=self.user1)
|
||||
self.assertEqual(len(accts), 1)
|
||||
@staticmethod
|
||||
def get_transaction_level():
|
||||
from django.db import connection
|
||||
cursor = connection.cursor()
|
||||
|
||||
accts = pm.retrieve_accounts(host='host1', user=self.user2)
|
||||
self.assertEqual(len(accts), 0)
|
||||
cursor.execute("SELECT * FROM information_schema.global_variables WHERE variable_name='tx_isolation';")
|
||||
row = cursor.fetchone()
|
||||
return row
|
||||
|
||||
|
||||
def test_status_changes(self):
|
||||
|
||||
def tearDown(self):
|
||||
# shouldn't have to clean up account2
|
||||
self.user1.delete()
|
||||
self.user2.delete()
|
||||
self.account1.delete()
|
||||
|
||||
from regluit.core.models import UserProfile
|
||||
|
||||
user1 = User.objects.create_user('account_test1', 'account_test1@gluejar.com', 'account_test1_pw')
|
||||
user1.save()
|
||||
|
||||
account1 = Account(host='host1', account_id='1', user=user1, status='ACTIVE')
|
||||
account1.save()
|
||||
|
||||
user = User.objects.all()[0]
|
||||
|
||||
account = user1.profile.account
|
||||
self.assertEqual(account.status, 'ACTIVE')
|
||||
account.status = 'EXPIRING'
|
||||
account.save()
|
||||
|
||||
self.assertEqual(account.status, 'EXPIRING')
|
||||
account.save()
|
||||
|
||||
user1.delete()
|
||||
account1.delete()
|
||||
|
||||
def suite():
|
||||
|
||||
#testcases = [AuthorizeTest, TransactionTest, CreditTest]
|
||||
testcases = [TransactionTest, CreditTest]
|
||||
#testcases = [PledgeTest, AuthorizeTest, TransactionTest]
|
||||
testcases = [TransactionTest, CreditTest, AccountTest]
|
||||
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
||||
return suites
|
||||
|
||||
|
|
|
@ -313,6 +313,18 @@ NOTIFY_ENDING_SOON_JOB = {
|
|||
"args": ()
|
||||
}
|
||||
|
||||
UPDATE_ACCOUNT_STATUSES = {
|
||||
"task": "regluit.payment.tasks.update_account_status",
|
||||
"schedule": crontab(day_of_month=1, hour=0, minute=0),
|
||||
"args": ()
|
||||
}
|
||||
|
||||
NOTIFY_EXPIRING_ACCOUNTS = {
|
||||
"task": "regluit.payment.tasks.notify_expiring_accounts",
|
||||
"schedule": crontab(day_of_month=22, hour=0, minute=30),
|
||||
"args": ()
|
||||
}
|
||||
|
||||
# by default, in common, we don't turn any of the celerybeat jobs on -- turn them on in the local settings file
|
||||
|
||||
# set notification queueing on
|
||||
|
|
|
@ -140,6 +140,8 @@ CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
|
|||
|
||||
CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
|
||||
|
||||
CELERYBEAT_SCHEDULE['update_account_statuses'] = UPDATE_ACCOUNT_STATUSES
|
||||
CELERYBEAT_SCHEDULE['notify_expiring_accounts'] = NOTIFY_EXPIRING_ACCOUNTS
|
||||
|
||||
# set -- sandbox or production Amazon FPS?
|
||||
AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
|
||||
|
|
|
@ -140,6 +140,8 @@ STATIC_ROOT = '/var/www/static'
|
|||
CELERYBEAT_SCHEDULE['update_active_campaign_statuses'] = UPDATE_ACTIVE_CAMPAIGN_STATUSES
|
||||
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
|
||||
CELERYBEAT_SCHEDULE['notify_ending_soon'] = NOTIFY_ENDING_SOON_JOB
|
||||
CELERYBEAT_SCHEDULE['update_account_statuses'] = UPDATE_ACCOUNT_STATUSES
|
||||
CELERYBEAT_SCHEDULE['notify_expiring_accounts'] = NOTIFY_EXPIRING_ACCOUNTS
|
||||
|
||||
# set -- sandbox or production Amazon FPS?
|
||||
#AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
|
||||
|
|
Loading…
Reference in New Issue