Merge remote-tracking branch 'Gluejar/master' into production

pull/91/head
eric 2018-02-21 12:47:58 -05:00
commit a6def2171d
22 changed files with 426 additions and 349 deletions

9
bookdata/sitemaps.txt Normal file
View File

@ -0,0 +1,9 @@
https://www.ubiquitypress.com/sitemap.xml
https://www.kriterium.se/sitemap.xml
https://oa.finlit.fi/sitemap.xml
https://www.humanities-map.net/sitemap.xml
https://oa.psupress.org/sitemap.xml
https://www.larcommons.net/sitemap.xml
https://www.uwestminsterpress.co.uk/sitemap.xml
https://www.stockholmuniversitypress.se/sitemap.xml
https://www.luminosoa.org/sitemap.xml

View File

@ -18,7 +18,7 @@ class UbiquityScraper(BaseScraper):
for desc in descs: for desc in descs:
if desc.find(string=HAS_EDS): if desc.find(string=HAS_EDS):
return 'editor' return 'editor'
return super(self, UbiquityScraper).get_role() return super(UbiquityScraper, self).get_role()
def get_language(self): def get_language(self):
langlabel = self.doc.find(string='Language') langlabel = self.doc.find(string='Language')
@ -28,4 +28,4 @@ class UbiquityScraper(BaseScraper):
if lang: if lang:
self.set('language', lang) self.set('language', lang)
else: else:
super(self, UbiquityScraper).get_language() super(UbiquityScraper, self).get_language()

View File

@ -1,9 +1,10 @@
import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from regluit.core.loaders import add_by_sitemap from regluit.core.loaders import add_by_sitemap
class Command(BaseCommand): class Command(BaseCommand):
help = "load books based on a website sitemap" help = "load books based on a website sitemap; use url=all to load from sitemap list"
def add_arguments(self, parser): def add_arguments(self, parser):
# Positional arguments # Positional arguments
@ -20,5 +21,20 @@ class Command(BaseCommand):
) )
def handle(self, url, max=None, **options): def handle(self, url, max=None, **options):
books = add_by_sitemap(url, maxnum=max) if url == 'all':
file_name = "../../../bookdata/sitemaps.txt"
command_dir = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(command_dir, file_name)
with open(file_path) as f:
content = f.readlines()
books = []
for sitemap in content:
added = add_by_sitemap(sitemap.strip(), maxnum=max)
max = max - len(added)
books = books + added
if max < 0:
break
else:
books = add_by_sitemap(url, maxnum=max)
print "loaded {} books".format(len(books)) print "loaded {} books".format(len(books))

View File

@ -85,7 +85,7 @@ def create_notice_types( **kwargs):
notification.create_notice_type("rights_holder_accepted", _("Agreement Accepted"), _("You have become a verified Unglue.it rights holder.")) notification.create_notice_type("rights_holder_accepted", _("Agreement Accepted"), _("You have become a verified Unglue.it rights holder."))
notification.create_notice_type("rights_holder_claim", _("Claim Entered"), _("A claim has been entered.")) notification.create_notice_type("rights_holder_claim", _("Claim Entered"), _("A claim has been entered."))
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("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_gift_credit", _("Gift Credit Balance"), _("You have a gift credit balance")) notification.create_notice_type("pledge_gift_credit", _("Credit Balance"), _("You have a credit balance"))
notification.create_notice_type("new_wisher", _("New wisher"), _("Someone new has faved a book that you're the rightsholder for")) notification.create_notice_type("new_wisher", _("New wisher"), _("Someone new has faved 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_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_expired", _("Credit Card Has Expired"), _("Your credit card has expired."))

View File

@ -13,24 +13,24 @@
{% endblock %} {% endblock %}
{% block doccontent %} {% block doccontent %}
<h2>gift Credits</h2> <h2>Unglue.it Credits</h2>
<p> <p>
You have a balance of {{ user.credit.balance }} gift credits. <br /> You have a balance of {{ user.credit.balance }} Unglue.it credits. <br />
You have pledged {{ user.credit.pledged }} gift credits to ungluing campaigns.<br /> You have pledged {{ user.credit.pledged }} Unglue.it credits to ungluing campaigns.<br />
You have {{ user.credit.available }} gift credits available to pledge or transfer.<br /> You have {{ user.credit.available }} Unglue.it credits available to pledge or transfer.<br />
</p> </p>
<div class="clearfix"> <div class="clearfix">
<h2>Gift Credit Transfers</h2> <h2>Unglue.it Credit Transfers</h2>
{% if transfer_message %} {% if transfer_message %}
<p>{{ transfer_message }} <p>{{ transfer_message }}
{% if transfer_amount %} {% if transfer_amount %}
<br /> Recipient: <a href="{% url 'supporter' recipient %}">{{ recipient }}</a> <br /> Recipient: <a href="{% url 'supporter' recipient %}">{{ recipient }}</a>
<br /> Amount: {{ transfer_amount }} gift credits <br /> Amount: {{ transfer_amount }} Unglue.it credits
{% endif %} {% endif %}
</p> </p>
{% endif %} {% endif %}
<p> <p>
You may transfer up to {{ user.credit.available }} gift credits to another Unglue.it user.<br /> You may transfer up to {{ user.credit.available }} Unglue.it credits to another Unglue.it user.<br />
</p> </p>
<form action="#" method="POST"> <form action="#" method="POST">
{% csrf_token %} {% csrf_token %}

View File

@ -2,7 +2,7 @@
{% load humanize %} {% load humanize %}
{% block title %}You Have Gift Credits{% endblock %} {% block title %}You Have Unglue.it Credits{% endblock %}
{% block doccontent %} {% block doccontent %}
<div style="height:15px"></div> <div style="height:15px"></div>
@ -13,9 +13,9 @@
<div> <div>
<h2> Gift Credited </h2> <h2> Gift Credited </h2>
<p>{% if error %} <p>{% if error %}
Your gift credit of ${{ envelope.amount }}.{{ envelope.cents }} has already been registered! {% if work %} If you want to contribute more to <a href="{% url 'work' work.id %}">{{ work.title }}</a>, you can! {% endif %} Your Unglue.it credit of ${{ envelope.amount }}.{{ envelope.cents }} has already been registered! {% if work %} If you want to contribute more to <a href="{% url 'work' work.id %}">{{ work.title }}</a>, you can! {% endif %}
{% else %} {% else %}
Congratulations, your gift credit of ${{ envelope.amount }}.{{ envelope.cents }} has been registered! {% if transaction.campaign %} ${{transaction.amount}} of that had been pledged to {{ transaction.campaign.name }}. If you want to contribute more to <a href="{% url 'work' work.id %}">{{ work.title }}</a>, you can! {% endif %} Congratulations, your Unglue.it credit of ${{ envelope.amount }}.{{ envelope.cents }} has been registered! {% if transaction.campaign %} ${{transaction.amount}} of that had been pledged to {{ transaction.campaign.name }}. If you want to contribute more to <a href="{% url 'work' work.id %}">{{ work.title }}</a>, you can! {% endif %}
{% endif %} {% endif %}
</p> </p>
<!-- sent log: {{ envelope.sent }} --> <!-- sent log: {{ envelope.sent }} -->
@ -24,9 +24,9 @@
<div> <div>
<h2> Your gift credits </h2> <h2> Your gift credits </h2>
<p> <p>
You have a balance of {{ request.user.credit.balance }} gift credits. <br /> You have a balance of {{ request.user.credit.balance }} Unglue.it credits. <br />
You have pledged {{ request.user.credit.pledged }} gift credits to ungluing campaigns.<br /> You have pledged {{ request.user.credit.pledged }} Unglue.it credits to ungluing campaigns.<br />
You have {{ request.user.credit.available }} gift credits available to pledge or <a href="{% url 'gift' %}">transfer</a>.<br /> You have {{ request.user.credit.available }} Unglue.it credits available to pledge or <a href="{% url 'gift' %}">transfer</a>.<br />
</p> </p>

View File

@ -10,9 +10,9 @@
<div class="jsmodule rounded clearfix"> <div class="jsmodule rounded clearfix">
<div class="jsmod-content"> <div class="jsmod-content">
<div><h2>Wrong user for gift credit</h2> <div><h2>Wrong user for Unglue.it credit</h2>
<div> <div>
<p>Unglue.it would like to process your gift credit, but you are currently logged in as <code>{{request.user.username}}</code>. Your gift credit for ${{ envelope.amount }}.{{ envelope.cents }} is designated for <code>{{ envelope.username }}</code>. To record your credit, you need to <a href='{% url 'auth_logout' %}?next={{ request.get_full_path|urlencode }}'>log out</a>, and then <a href='{% url 'superlogin' %}?next={{ request.get_full_path|urlencode }}'>log in</a> as <code>{{ envelope.username }}</code>. If you have any problem, don't hesitate to <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">contact us</a>. <p>Unglue.it would like to process your Unglue.it credit, but you are currently logged in as <code>{{request.user.username}}</code>. Your Unglue.it credit for ${{ envelope.amount }}.{{ envelope.cents }} is designated for <code>{{ envelope.username }}</code>. To record your credit, you need to <a href='{% url 'auth_logout' %}?next={{ request.get_full_path|urlencode }}'>log out</a>, and then <a href='{% url 'superlogin' %}?next={{ request.get_full_path|urlencode }}'>log in</a> as <code>{{ envelope.username }}</code>. If you have any problem, don't hesitate to <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">contact us</a>.
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,12 +1,12 @@
{% extends 'notification/base.html' %} {% extends 'notification/base.html' %}
{% load sass_tags %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Notices" %}{% endblock %} {% block title %}{% trans "Notices" %}{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/notices.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/notices.scss' %}" />
{% endblock %} {% endblock %}
{% block doccontent %} {% block doccontent %}
@ -23,24 +23,28 @@
<div class="notices_menu"> <div class="notices_menu">
<a href="{% url 'notification_mark_all_seen' %}">{% trans "Mark all notices seen" %}</a> <a href="{% url 'notification_mark_all_seen' %}">{% trans "Mark all notices seen" %}</a>
</div> </div>
{% elif notice.unseen or request.GET.mode != 'seen' %}
<div class="notices_menu">
<a href="{% url 'notification_notices' %}?mode=seen">{% trans "Show seen notices" %}</a>
</div>
{% else %}
<div class="notices_menu">
<a href="{% url 'notification_notices' %}">{% trans "Show only unseen notices" %}</a>
</div>
{% endif %} {% endif %}
<div class="comments"></div> <div class="comments"></div>
<br /> <br />
{% if unseen_count > 0 %} {% if unseen_count > 0 or request.GET.mode == 'seen' %}
{% for notice in notices %} {% for notice in notices %}
{% if notice.unseen %} {% if notice.unseen or request.GET.mode == 'seen' %}
{% comment %}
Note: do not call is_unseen because it will mark the notification as seen,
and we're leaving that up to the user.
{% endcomment %}
<div class="notice"> <div class="notice">
{{ notice.message|safe }} {{ notice.message|safe }}
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% else %} {% else %}
<p>{% trans "You have no unseen notices." %}</p> <p>You have no {% if unseen_count == 0 %}unseen {% endif %} notices.</p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
{% load humanize %}{% if transaction.donation %}{% ifequal transaction.host 'credit' %}Your Unglue.it transaction has completed and ${{transaction.max_amount|default:"0"}} has been deducted from your Unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}{% if transaction.max_amount > transaction.amount %}Your transaction for ${{transaction.max_amount|default:"0"}} has completed. Your credit card has been charged ${{transaction.amount}} and the rest has been deducted from your unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|default:"0" }}. {% endif %}{% endifequal %} {% load humanize %}{% if transaction.donation %}{% if transaction.host == 'credit' %}Your Unglue.it transaction has completed and ${{transaction.max_amount|default:"0"}} has been deducted from your Unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% elif transaction.max_amount > transaction.amount %}Your transaction for ${{transaction.max_amount|default:"0"}} has completed. Your credit card has been charged ${{transaction.amount}} and the rest has been deducted from your unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|default:"0" }}. {% endif %}
Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will support our effort to release {{ transaction.campaign.work.title }} to the world in an unglued ebook edition. We'll email you if the campaign succeeds, and when the ebook is available for download. If you'd like to visit the campaign page, click here: Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will support our effort to release {{ transaction.campaign.work.title }} to the world in an unglued ebook edition. We'll email you if the campaign succeeds, and when the ebook is available for download. If you'd like to visit the campaign page, click here:
https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %} https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %}
@ -15,7 +15,7 @@ Thank you again for your generous support.
{% else %}An Ungluing! {% else %}An Ungluing!
Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. Your {% if transaction.host == 'credit' %}Unglue.it credit account{% else %}credit card{% endif %} has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
Pledge summary Pledge summary
{% include "notification/pledge_summary.txt" %} {% include "notification/pledge_summary.txt" %}

View File

@ -40,7 +40,7 @@
{% else %} {% else %}
<p>Congratulations!</p> <p>Congratulations!</p>
<p>Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. {{ transaction.host|capfirst }} has been charged to your credit card.</p> <p>Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. Your {% if transaction.host == 'credit' %}Unglue.it credit account{% else %}credit card{% endif %} has been charged ${{ transaction.amount|floatformat:2|intcomma }}.</p>
<p><b>Pledge Summary</b><br /> <p><b>Pledge Summary</b><br />
Amount pledged: {{ transaction.amount|floatformat:2|intcomma }}<br /> Amount pledged: {{ transaction.amount|floatformat:2|intcomma }}<br />

View File

@ -1,19 +1,19 @@
{% if amount > 0 %} {% if amount > 0 %}
{% if amount == 1 %} {% if amount == 1 %}
{{ user.username }}, 1 gift credit has been added to your unglue.it gift credit account. {{ user.username }}, 1 Unglue.it credit has been added to your unglue.it gift credit account.
{% else %} {% else %}
{{ user.username }}, {{ amount }} gift credits have been added to your unglue.it gift credit account. {{ user.username }}, {{ amount }} Unglue.it credits have been added to your unglue.it gift credit account.
{% endif %} {% endif %}
{% else %} {% else %}
{% if amount == 1 %} {% if amount == 1 %}
{{ user.username }}, 1 gift credit has been deducted from your unglue.it gift credit account. {{ user.username }}, 1 Unglue.it credit has been deducted from your unglue.it gift credit account.
{% else %} {% else %}
{{ user.username }}, {{ minus_amount }} gift credits have been deducted from your unglue.it gift credit account. {{ user.username }}, {{ minus_amount }} Unglue.it credits have been deducted from your unglue.it gift credit account.
{% endif %} {% endif %}
{% endif %} {% endif %}
You have a balance of {{ user.credit.balance }} gift credits. You have a balance of {{ user.credit.balance }} Unglue.it credits.
You have pledged {{ user.credit.pledged }} gift credits to ungluing campaigns. You have pledged {{ user.credit.pledged }} Unglue.it credits to ungluing campaigns.
You have {{ user.credit.available }} gift credits available to pledge or transfer. You have {{ user.credit.available }} Unglue.it credits available to pledge or transfer.
You can manage your gift credit account at https://unglue.it/gift/ You can manage your Unglue.it credit account at https://unglue.it/gift/
Gift credits can be used in support of any type of ungluing campaign. Unglue.it credits can be used in support of any type of ungluing campaign.

View File

@ -2,26 +2,26 @@
{% block comments_graphical %} {% block comments_graphical %}
{% if amount > 0 %} {% if amount > 0 %}
{{ user.username }}, {{ amount }} gift credits have been added to your unglue.it gift credit account. {{ user.username }}, {{ amount }} Unglue.it credits have been added to your unglue.it Unglue.it credit account.
{% else %} {% else %}
{{ user.username }}, {{ minus_amount }} gift credits have been deducted from your unglue.it gift credit account. {{ user.username }}, {{ minus_amount }} Unglue.it credits have been deducted from your unglue.it Unglue.it credit account.
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block comments_textual %} {% block comments_textual %}
<p> <p>
You have a balance of {{ user.credit.balance }} gift credits. You have a balance of {{ user.credit.balance }} Unglue.it credits.
</p> </p>
<p> <p>
You have pledged {{ user.credit.pledged }} gift credits to ungluing campaigns. You have pledged {{ user.credit.pledged }} Unglue.it credits to ungluing campaigns.
</p> </p>
<p> <p>
You have {{ user.credit.available }} gift credits available to pledge or transfer. You have {{ user.credit.available }} Unglue.it credits available to pledge or transfer.
</p> </p>
<p> <p>
You can manage your gift credit account <a href="https://unglue.it/gift/">here</a> You can manage your Unglue.it credit account <a href="https://unglue.it/gift/">here</a>
</p> </p>
<p> <p>
Gift credits can be used in support of ungluing campaigns. Unglue.it credits can be used in support of ungluing campaigns.
</p> </p>
{% endblock %} {% endblock %}

View File

@ -1 +1 @@
{% if amount > 0 %}{{ amount }} gift credits have been added to your unglue.it gift credit account.{% else %}{{ minus_amount }} gift credits have been deducted from your unglue.it gift credit account.{% endif %} {% if amount > 0 %}{{ amount }} Unglue.it credits have been added to your unglue.it credit account.{% else %}{{ minus_amount }} Unglue.it credits have been deducted from your unglue.it credit account.{% endif %}

View File

@ -1,20 +1,13 @@
{% if work.last_campaign_status == 'SUCCESSFUL' %} New ebook files for "{{ work.title }}", which is on your ungluing list, are available for download.
{{ work.title }}, which is on your ungluing list, is now available for download as an Unglued Ebook.
{% else %} Here are the files now available:
{{ work.title }}, which is on your ungluing list, is available for download as a {{ work.ebooks.0.get_rights_display }} ebook.
{% if work.ebooks.0.user %}
We'd like to thank Ungluer {{work.ebooks.0.user}} for adding the link(s).
{% endif %}
{% endif %}
Here are the files available for download:
{% for ebook in work.ebooks %} {% for ebook in work.ebooks %}
File type: {{ ebook.get_format_display }} File type: {{ ebook.get_format_display }}
License: {{ ebook.get_rights_display }} {% if ebook.version_label %}Version: {{ ebook.version_label }}
{% endif %}License: {{ ebook.get_rights_display }}
Host Site: {{ ebook.provider }} Host Site: {{ ebook.provider }}
URL: {{ ebook.download_url }} URL: {{ ebook.download_url }}
Date Added: {{ ebook.created|date:"M d, Y" }}
{% endfor %} {% endfor %}
{% if work.ebooks.0.rights == 'PD-US' %} {% if work.ebooks.0.rights == 'PD-US' %}

View File

@ -11,35 +11,26 @@
{% endblock %} {% endblock %}
{% block comments_textual %} {% block comments_textual %}
{% if work.last_campaign_status == 'SUCCESSFUL' %}
<p> <p>
Great News! <a href="{% url 'work' work.id %}">{{ work.title }}</a> which is on your ungluing list is now available for download as an Unglued Ebook. New ebook files for <a href="{% url 'work' work.id %}">{{ work.title }}</a>, are available for download.
</p> </p>
{% else %}
<p>
Good News! <a href="{% url 'work' work.id %}">{{ work.title }}</a> which is on your ungluing list is available for download as a {{ work.ebooks.0.get_rights_display }} ebook.
</p>
{% if work.ebooks.0.user %}
<p>
We'd like to thank Ungluer {{work.ebooks.0.user}} for adding the link(s).
</p>
{% endif %}
{% endif %}
<p>Here are the files available for download:</p> <p>Here are the files now available for download:</p>
<table> <table>
<tr> <tr>
<th>File type</th> <th>File type</th>
<th>Version</th>
<th>License</th> <th>License</th>
<th>Host Site</th>
<th>URL</th> <th>URL</th>
<th>Date Added</th>
</tr> </tr>
{% for ebook in work.ebooks %} {% for ebook in work.ebooks %}
<tr> <tr>
<td>{{ ebook.get_format_display }}</td> <td>{{ ebook.get_format_display }}</td>
<td>{{ ebook.version_label }}</td>
<td>{{ ebook.get_rights_display }}</td> <td>{{ ebook.get_rights_display }}</td>
<td>{{ ebook.provider }}</td>
<td><a href="{{ ebook.download_url }}">{{ ebook.download_url|truncatechars:30 }}</a></td> <td><a href="{{ ebook.download_url }}">{{ ebook.download_url|truncatechars:30 }}</a></td>
<td>{{ ebook.created|date:"M d, Y" }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -1 +1 @@
{{ work.title }} is available for download! New ebook files for {{ work.title }} are available for download!

View File

@ -177,7 +177,7 @@
<input name="pledge" type="submit" value="Buy Now" id="pledgesubmit" class="loader-gif" /> <input name="pledge" type="submit" value="Buy Now" id="pledgesubmit" class="loader-gif" />
<input name="decoy" type="submit" id="fakepledgesubmit" disabled="disabled" /> <input name="decoy" type="submit" id="fakepledgesubmit" disabled="disabled" />
{% if request.user.credit.available > 0 %} {% if request.user.credit.available > 0 %}
<div class="bigger" style="height:45px;clear:both"> You have an available gift credit of ${{ request.user.credit.available|intcomma }} which will be applied to your purchase.</div> <div class="bigger" style="height:45px;clear:both"> You have an available Unglue.it credit of ${{ request.user.credit.available|intcomma }} which will be applied to your purchase.</div>
{% endif %} {% endif %}
</form> </form>

View File

@ -1,27 +1,31 @@
from datetime import timedelta from datetime import datetime
from django.conf import settings
from django.contrib.auth.models import User
from regluit.payment import baseprocessor from regluit.payment import baseprocessor
from regluit.payment.baseprocessor import BasePaymentRequest from regluit.payment.baseprocessor import BasePaymentRequest
from regluit.payment.parameters import * from regluit.payment.parameters import (
PAYMENT_HOST_CREDIT,
PAYMENT_TYPE_AUTHORIZATION,
PAYMENT_TYPE_INSTANT,
TRANSACTION_STATUS_COMPLETE,
TRANSACTION_STATUS_CANCELED,
)
from regluit.payment.signals import transaction_charged
def pledge_transaction(t,user,amount): def pledge_transaction(t, user, amount):
"""commit <amount> from a <user>'s credit to a specified transaction <t>""" """commit <amount> from a <user>'s credit to a specified transaction <t>"""
if t.amount and t.host == PAYMENT_HOST_CREDIT: if t.amount and t.host == PAYMENT_HOST_CREDIT:
#changing the pledge_transaction #changing the pledge_transaction
success = user.credit.add_to_pledged(amount-t.amount) success = user.credit.add_to_pledged(amount-t.amount)
else: else:
success = user.credit.add_to_pledged(amount) success = user.credit.add_to_pledged(amount)
if success: if success:
t.type = PAYMENT_TYPE_AUTHORIZATION t.type = PAYMENT_TYPE_AUTHORIZATION
t.max_amount=amount t.max_amount = amount
t.set_credit_approved(amount) t.set_credit_approved(amount)
return success return success
def credit_transaction(t,user,amount): def credit_transaction(t, user, amount):
'''user has new credit, use it to fund the transaction''' '''user has new credit, use it to fund the transaction'''
# first, credit the user's account # first, credit the user's account
success = user.credit.add_to_balance(amount) success = user.credit.add_to_balance(amount)
@ -35,7 +39,7 @@ def credit_transaction(t,user,amount):
def pay_transaction(t, user, to_user, amount): def pay_transaction(t, user, to_user, amount):
'''user has credit, transfer it to rh account''' '''user has credit, transfer it to rh account'''
success = user.credit.transfer_to(to_user , amount) success = user.credit.transfer_to(to_user, amount)
if success: if success:
t.type = PAYMENT_TYPE_INSTANT t.type = PAYMENT_TYPE_INSTANT
t.set_executed() t.set_executed()
@ -44,19 +48,19 @@ def pay_transaction(t, user, to_user, amount):
class Processor(baseprocessor.Processor): class Processor(baseprocessor.Processor):
class CancelPreapproval(BasePaymentRequest): class CancelPreapproval(BasePaymentRequest):
''' '''
Cancels an exisiting token. Cancels an exisiting token.
''' '''
def __init__(self, transaction): def __init__(self, transaction):
self.transaction = transaction self.transaction = transaction
if transaction.user.credit.add_to_pledged(-transaction.amount): if transaction.user.credit.add_to_pledged(-transaction.amount):
#success #success
transaction.status=TRANSACTION_STATUS_CANCELED transaction.status = TRANSACTION_STATUS_CANCELED
transaction.save() transaction.save()
else: else:
self.errorMessage="couldn't cancel the transaction" self.errorMessage = "couldn't cancel the transaction"
self.status = 'Credit Cancel Failure' self.status = 'Credit Cancel Failure'
class PreapprovalDetails(BasePaymentRequest): class PreapprovalDetails(BasePaymentRequest):
status = None status = None
approved = None approved = None
@ -67,3 +71,25 @@ class Processor(baseprocessor.Processor):
self.approved = transaction.approved self.approved = transaction.approved
self.currency = transaction.currency self.currency = transaction.currency
self.amount = transaction.amount self.amount = transaction.amount
class Execute(BasePaymentRequest):
'''
This Execute function debits the user credits and pledge and credits the recipient.
'''
def __init__(self, transaction=None):
self.transaction = transaction
amount = transaction.amount
# make sure transaction hasn't already been executed
if transaction.status == TRANSACTION_STATUS_COMPLETE:
return
used = transaction.user.credit.use_pledge(amount)
if used:
user_to_pay = transaction.campaign.user_to_pay
credited = user_to_pay.credit.add_to_balance(amount, notify=False)
transaction.status = TRANSACTION_STATUS_COMPLETE
transaction.date_payment = datetime.now()
transaction.save()
# fire signal for sucessful transaction
transaction_charged.send(sender=self, transaction=transaction)

View File

@ -1,16 +1,24 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth.models import User from django.contrib.auth.models import User
class Command(BaseCommand): class Command(BaseCommand):
help = "grant credit to a user" help = "grant (or debit or redeem) credit to a user. \
Usage: grant_user_credit <username> <amount> <action>\
amount is dollars or 'all' "
args = "<username> <amount> <action>" args = "<username> <amount> <action>"
def handle(self, username, amount, action="credit", *args, **kwargs): def handle(self, username, amount, action="credit", *args, **kwargs):
if action=="debit": if action not in ("debit", "redeem", "credit"):
amount=-int(amount) print 'action should be in ("debit", "redeem", "credit")'
else: return
amount= int(amount)
user = User.objects.get(username=username) user = User.objects.get(username=username)
user.credit.add_to_balance(amount) if amount == 'all':
print "%s now has a balance of %s gift credits" % (username, user.credit.balance) amount = user.credit.available
if action in ("debit", "redeem" ):
amount = -int(amount)
elif action == "credit":
amount = int(amount)
notify = action != "redeem"
user.credit.add_to_balance(amount, notify=notify)
print "{}ed ${} from {}".format(action, amount, username)
print "{} now has a balance of {} credits".format(username, user.credit.balance)

View File

@ -323,7 +323,7 @@ class PaymentManager( object ):
# only allow active transactions to go through again, if there is an error, intervention is needed # only allow active transactions to go through again, if there is an error, intervention is needed
transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACTION_STATUS_ACTIVE) transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACTION_STATUS_ACTIVE)
results = [] results = []
for t in transactions: for t in transactions:
@ -461,8 +461,6 @@ class PaymentManager( object ):
# Mark as payment attempted so we will poll this periodically for status changes # Mark as payment attempted so we will poll this periodically for status changes
transaction.set_payment() transaction.set_payment()
# here's where we need to add handling for credit transactions in pledge campaigns
p = transaction.get_payment_class().Execute(transaction) p = transaction.get_payment_class().Execute(transaction)
# Create a response for this # Create a response for this

View File

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

View File

@ -16,7 +16,7 @@ input[type="submit"], a.fakeinput {
padding: 10px; padding: 10px;
font-size: $font-size-header; font-size: $font-size-header;
background: $pale-blue; background: $pale-blue;
&.premium_level { &.premium_level {
margin-top: 3px; margin-top: 3px;
} }
@ -24,7 +24,7 @@ input[type="submit"], a.fakeinput {
form.pledgeform { form.pledgeform {
width: 470px; width: 470px;
.pledgeform_label { .pledgeform_label {
font-size: 80% font-size: 80%
} }
@ -46,7 +46,7 @@ p {
.jsmodule.pledge { .jsmodule.pledge {
margin: auto; margin: auto;
.jsmod-content { .jsmod-content {
float: right !important; float: right !important;
} }
@ -58,7 +58,7 @@ p {
border: solid 2px $blue-grey; border: solid 2px $blue-grey;
@include one-border-radius(5px); @include one-border-radius(5px);
padding: 7px; padding: 7px;
h4 { h4 {
margin: 0 0 5px 0; margin: 0 0 5px 0;
} }
@ -83,7 +83,7 @@ span.menu-item-price {
} }
ul#offers_list li { ul#offers_list li {
div.on { div.on {
display: block; display: block;
background: $pale-blue; background: $pale-blue;
@ -109,10 +109,10 @@ ul#offers_list li {
} }
.premiums_inactive#select_premiums { .premiums_inactive#select_premiums {
background: $blue-grey; background: $blue-grey;
div, div.pledge_amount, ul li, ul li:hover { div, div.pledge_amount, ul li, ul li:hover {
background: $blue-grey; background: $blue-grey;
color: $text-blue color: $text-blue
} }
} }
@ -127,24 +127,24 @@ ul#offers_list li {
div { div {
float: left; float: left;
&.ack_level { &.ack_level {
width: 16%; width: 16%;
margin-right: 3%; margin-right: 3%;
height: 100%; height: 100%;
padding: 1%; padding: 1%;
} }
&.ack_header { &.ack_header {
width: 73%; width: 73%;
padding: 1%; padding: 1%;
} }
&.ack_active, &.ack_inactive { &.ack_active, &.ack_inactive {
width: 100%; width: 100%;
font-size: $font-size-default; font-size: $font-size-default;
} }
&.ack_active { &.ack_active {
.ack_header, .ack_level { .ack_header, .ack_level {
border: solid $text-blue; border: solid $text-blue;
@ -152,12 +152,12 @@ ul#offers_list li {
background: white; background: white;
} }
} }
&.ack_inactive { &.ack_inactive {
.ack_header, .ack_level { .ack_header, .ack_level {
border: solid $blue-grey; border: solid $blue-grey;
border-width: 1%; border-width: 1%;
background: $blue-grey; background: $blue-grey;
} }
input, textarea { input, textarea {
@ -166,11 +166,11 @@ ul#offers_list li {
} }
} }
} }
> div { > div {
margin: 7px 0; margin: 7px 0;
} }
input[type=text], textarea { input[type=text], textarea {
width: 95%; width: 95%;
font-size: $font-size-larger; font-size: $font-size-larger;
@ -195,39 +195,39 @@ ul#offers_list li {
float: left; float: left;
line-height: normal; line-height: normal;
} }
input[type="submit"] { input[type="submit"] {
float: left; float: left;
} }
div { div {
width: 50%; width: 50%;
float: left; float: left;
ul { ul {
background: $pale-blue; background: $pale-blue;
} }
&.highlight { &.highlight {
ul { ul {
border-color: $medium-blue-grey; border-color: $medium-blue-grey;
background: white; background: white;
} }
color: $dark-green; color: $dark-green;
} }
} }
ul { ul {
padding: 5px 10px 5px 15px; padding: 5px 10px 5px 15px;
border: solid 1px $blue-grey; border: solid 1px $blue-grey;
margin-right: 15px; margin-right: 15px;
list-style-type: none; list-style-type: none;
li { li {
margin: 5px 0; margin: 5px 0;
} }
a { a {
color: $medium-blue; color: $medium-blue;
} }
@ -238,12 +238,12 @@ ul#offers_list li {
&.off { &.off {
display: none; display: none;
} }
border: 3px solid $blue-grey; border: 3px solid $blue-grey;
@include one-border-radius(5px); @include one-border-radius(5px);
margin-top: 10px; margin-top: 10px;
padding: 10px; padding: 10px;
div.innards { div.innards {
input[type="text"],input[type="password"] { input[type="text"],input[type="password"] {
font-size: $font-size-larger; font-size: $font-size-larger;
@ -252,16 +252,16 @@ ul#offers_list li {
padding: 1% 1%; padding: 1% 1%;
margin: 1% 0; margin: 1% 0;
color: $text-blue; color: $text-blue;
&:disabled { &:disabled {
border-color: white; border-color: white;
} }
&.address, &#card_Number, &#id_email { &.address, &#card_Number, &#id_email {
width: 61%; width: 61%;
} }
} }
label { label {
width: 31%; width: 31%;
float: left; float: left;
@ -272,8 +272,8 @@ ul#offers_list li {
padding: 1% 2% 1% 0; padding: 1% 2% 1% 0;
margin: 1% 0; margin: 1% 0;
text-align: right; text-align: right;
} }
.form-row span { .form-row span {
float: left; float: left;
line-height: $font-size-larger*1.5; line-height: $font-size-larger*1.5;
@ -282,12 +282,12 @@ ul#offers_list li {
padding: 1% 0; padding: 1% 0;
} }
} }
.cvc { .cvc {
position: relative; position: relative;
z-index: 0; z-index: 0;
} }
#cvc_help { #cvc_help {
font-style: italic; font-style: italic;
float: none; float: none;
@ -295,7 +295,7 @@ ul#offers_list li {
color: $link-color; color: $link-color;
cursor: pointer; cursor: pointer;
} }
#cvc_answer { #cvc_answer {
display: none; display: none;
z-index: 100; z-index: 100;
@ -309,7 +309,7 @@ ul#offers_list li {
right: 0; right: 0;
opacity: 1; opacity: 1;
background-color: white; background-color: white;
img { img {
float: right; float: right;
margin-left: 5px; margin-left: 5px;