add in credi processing; overhaul the PledgeView forms and partition authorize fn in Manager

also removed unused target, lists, ack-link,
pull/1/head
eric 2012-08-31 03:16:04 -04:00
parent f299d9ecf7
commit 94270f33c6
13 changed files with 841 additions and 494 deletions

View File

@ -0,0 +1,250 @@
# encoding: 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 model 'Badge'
# first drop tables created by syncdb
db.create_table('core_badge', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=72, blank=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', null=True)),
))
db.send_create_signal('core', ['Badge'])
# Adding M2M table for field badges on 'UserProfile'
db.create_table('core_userprofile_badges', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('userprofile', models.ForeignKey(orm['core.userprofile'], null=False)),
('badge', models.ForeignKey(orm['core.badge'], null=False))
))
db.create_unique('core_userprofile_badges', ['userprofile_id', 'badge_id'])
def backwards(self, orm):
# Deleting model 'Badge'
db.delete_table('core_badge')
# Removing M2M table for field badges on 'UserProfile'
db.delete_table('core_userprofile_badges')
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(2012, 8, 31, 2, 9, 51, 31703)'}),
'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(2012, 8, 31, 2, 9, 51, 31561)'}),
'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.author': {
'Meta': {'object_name': 'Author'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500'})
},
'core.badge': {
'Meta': {'object_name': 'Badge'},
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '72', 'blank': 'True'})
},
'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', [], {}),
'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.campaignaction': {
'Meta': {'object_name': 'CampaignAction'},
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['core.Campaign']"}),
'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '15'})
},
'core.celerytask': {
'Meta': {'object_name': 'CeleryTask'},
'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 31, 2, 9, 50, 804173)', 'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}),
'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'null': 'True', 'to': "orm['auth.User']"})
},
'core.claim': {
'Meta': {'object_name': 'Claim'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rights_holder': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.RightsHolder']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '7'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['auth.User']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.Work']"})
},
'core.ebook': {
'Meta': {'object_name': 'Ebook'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebooks'", 'to': "orm['core.Edition']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'rights': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'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'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.identifier': {
'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"})
},
'core.key': {
'Meta': {'object_name': 'Key'},
'encrypted_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
'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.rightsholder': {
'Meta': {'object_name': 'RightsHolder'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rights_holder'", 'to': "orm['auth.User']"}),
'rights_holder_name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.subject': {
'Meta': {'ordering': "['name']", 'object_name': 'Subject'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", 'symmetrical': 'False', 'to': "orm['core.Work']"})
},
'core.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'badges': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'holders'", 'symmetrical': 'False', 'to': "orm['core.Badge']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'facebook_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'goodreads_auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_user_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'goodreads_user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'goodreads_user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
'pic_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'tagline': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
'twitter_id': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'core.waswork': {
'Meta': {'object_name': 'WasWork'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'moved': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
'was': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'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'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
}
}
complete_apps = ['core']

View File

@ -296,16 +296,15 @@ class CampaignPledgeForm(forms.Form):
) )
anonymous = forms.BooleanField(required=False, label=_("Don't display my name in the acknowledgements")) anonymous = forms.BooleanField(required=False, label=_("Don't display my name in the acknowledgements"))
ack_name = forms.CharField(required=False, max_length=64, label=_("What name should we display?")) ack_name = forms.CharField(required=False, max_length=64, label=_("What name should we display?"))
ack_link = forms.URLField(required=False, label=_("Your web site:"))
ack_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:")) ack_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:"))
premium_id = forms.IntegerField(required=False) premium_id = forms.IntegerField(required=False)
def clean_preapproval_amount(self): def clean_preapproval_amount(self):
data = self.cleaned_data['preapproval_amount'] preapproval_amount = self.cleaned_data['preapproval_amount']
if data is None: if preapproval_amount is None:
raise forms.ValidationError(_("Please enter a pledge amount.")) raise forms.ValidationError(_("Please enter a pledge amount."))
return data return preapproval_amount
# should we do validation on the premium_id here? # should we do validation on the premium_id here?
# can see whether it corresponds to a real premium -- do that here? # can see whether it corresponds to a real premium -- do that here?
@ -317,11 +316,6 @@ class CampaignPledgeForm(forms.Form):
try: try:
preapproval_amount = cleaned_data.get("preapproval_amount") preapproval_amount = cleaned_data.get("preapproval_amount")
premium_id = int(cleaned_data.get("premium_id")) premium_id = int(cleaned_data.get("premium_id"))
premium_amount = Premium.objects.get(id=premium_id).amount
logger.info("preapproval_amount: {0}, premium_id: {1}, premium_amount:{2}".format(preapproval_amount, premium_id, premium_amount))
if preapproval_amount < premium_amount:
logger.info("raising form validating error")
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (premium_amount)))
try: try:
premium= Premium.objects.get(id=premium_id) premium= Premium.objects.get(id=premium_id)
if premium.limit>0: if premium.limit>0:
@ -329,6 +323,11 @@ class CampaignPledgeForm(forms.Form):
raise forms.ValidationError(_("Sorry, that premium is fully subscribed.")) raise forms.ValidationError(_("Sorry, that premium is fully subscribed."))
except Premium.DoesNotExist: except Premium.DoesNotExist:
raise forms.ValidationError(_("Sorry, that premium is not valid.")) raise forms.ValidationError(_("Sorry, that premium is not valid."))
premium_amount = premium.amount
logger.info("preapproval_amount: {0}, premium_id: {1}, premium_amount:{2}".format(preapproval_amount, premium_id, premium_amount))
if preapproval_amount < premium_amount:
logger.info("raising form validating error")
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (premium_amount)))
except Exception, e: except Exception, e:
if isinstance(e, forms.ValidationError): if isinstance(e, forms.ValidationError):

View File

@ -108,6 +108,8 @@
</form> </form>
</div> </div>
</div> </div>
<div class="cancel_notice">
{% if faqmenu == 'modify' %}We hope you won't, but of course you're also free to <a href="{% url pledge_cancel campaign_id=work.last_campaign.id %}">cancel your pledge</a>.{% endif %}</div>
<div id="pass_supporter_name" style="display: none;">{{ request.user.username }}</div> <div id="pass_supporter_name" style="display: none;">{{ request.user.username }}</div>
{% if transaction.ack_name %} {% if transaction.ack_name %}
<div id="pass_ack_name" style="display: none;">{{ transaction.ack_name }}</div> <div id="pass_ack_name" style="display: none;">{{ transaction.ack_name }}</div>

View File

@ -7,7 +7,7 @@ from django.conf import settings
from regluit.core.feeds import SupporterWishlistFeed from regluit.core.feeds import SupporterWishlistFeed
from regluit.core.models import Campaign from regluit.core.models import Campaign
from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeModifyView, PledgeCancelView, PledgeNeverMindView, PledgeRechargeView, FAQView from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeCancelView, PledgeRechargeView, FAQView
from regluit.frontend.views import CampaignListView, DonateView, WorkListView, UngluedListView, InfoPageView, InfoLangView, DonationView from regluit.frontend.views import CampaignListView, DonateView, WorkListView, UngluedListView, InfoPageView, InfoLangView, DonationView
urlpatterns = patterns( urlpatterns = patterns(
@ -55,8 +55,7 @@ urlpatterns = patterns(
url(r"^pledge/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"), url(r"^pledge/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"),
url(r"^pledge/cancel/(?P<campaign_id>\d+)$", login_required(PledgeCancelView.as_view()), name="pledge_cancel"), url(r"^pledge/cancel/(?P<campaign_id>\d+)$", login_required(PledgeCancelView.as_view()), name="pledge_cancel"),
url(r"^pledge/complete/$", login_required(PledgeCompleteView.as_view()), name="pledge_complete"), url(r"^pledge/complete/$", login_required(PledgeCompleteView.as_view()), name="pledge_complete"),
url(r"^pledge/nevermind/$", login_required(PledgeNeverMindView.as_view()), name="pledge_nevermind"), url(r"^pledge/modify/(?P<work_id>\d+)$", login_required(PledgeView.as_view()), name="pledge_modify"),
url(r"^pledge/modify/(?P<work_id>\d+)$", login_required(PledgeModifyView.as_view()), name="pledge_modify"),
url(r"^pledge/recharge/(?P<work_id>\d+)$", login_required(PledgeRechargeView.as_view()), name="pledge_recharge"), url(r"^pledge/recharge/(?P<work_id>\d+)$", login_required(PledgeRechargeView.as_view()), name="pledge_recharge"),
url(r"^subjects/$", "subjects", name="subjects"), url(r"^subjects/$", "subjects", name="subjects"),
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"), url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),

View File

@ -50,8 +50,8 @@ from regluit.frontend.forms import EbookForm, CustomPremiumForm, EditManagersFor
from regluit.frontend.forms import getTransferCreditForm from regluit.frontend.forms import getTransferCreditForm
from regluit.payment.manager import PaymentManager from regluit.payment.manager import PaymentManager
from regluit.payment.models import Transaction from regluit.payment.models import Transaction
from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION, PAYMENT_TYPE_AUTHORIZATION
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION
from regluit.core import goodreads from regluit.core import goodreads
from tastypie.models import ApiKey from tastypie.models import ApiKey
from regluit.payment.models import Transaction from regluit.payment.models import Transaction
@ -583,280 +583,142 @@ class DonationView(TemplateView):
class PledgeView(FormView): class PledgeView(FormView):
template_name="pledge.html" template_name="pledge.html"
form_class = CampaignPledgeForm form_class = CampaignPledgeForm
transaction = None
campaign = None
work = None
premiums = None
data = None
def get(self, request, *args, **kwargs): def get_form_kwargs(self):
# change the default behavior from https://code.djangoproject.com/browser/django/tags/releases/1.3.1/django/views/generic/edit.py#L129
# don't automatically bind the data to the form on GET, only on POST
# compare with https://code.djangoproject.com/browser/django/tags/releases/1.3.1/django/views/generic/edit.py#L34
form_class = self.get_form_class()
form = form_class()
context_data = self.get_context_data(form=form)
# if there is already an active campaign pledge for user, redirect to the pledge modify page
if context_data.get('redirect_to_modify_pledge'):
work = context_data['work']
return HttpResponseRedirect(reverse('pledge_modify', args=[work.id]))
else:
return self.render_to_response(context_data)
def get_context_data(self, **kwargs):
"""set up the pledge page"""
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
assert self.request.user.is_authenticated() assert self.request.user.is_authenticated()
user = self.request.user self.work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
context = super(PledgeView, self).get_context_data(**kwargs)
work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
campaign = work.last_campaign()
# if there is no campaign or if campaign is not active, we should raise an error # if there is no campaign or if campaign is not active, we should raise an error
try:
if campaign is None or campaign.status != 'ACTIVE': self.campaign = self.work.last_campaign()
raise Http404 # TODO need to sort the premiums
self.premiums = self.campaign.custom_premiums() | models.Premium.objects.filter(id=150)
custom_premiums = campaign.custom_premiums() # Campaign must be ACTIVE
# need to also include the no-premiums default in the queryset we send the page assert self.campaign.status == 'ACTIVE'
premiums = custom_premiums | models.Premium.objects.filter(id=150) except Exception, e:
premium_id = self.request.REQUEST.get('premium_id', None) raise e
preapproval_amount = self.request.REQUEST.get('preapproval_amount', None)
transactions = self.campaign.transactions().filter(user=self.request.user, status=TRANSACTION_STATUS_ACTIVE, type=PAYMENT_TYPE_AUTHORIZATION)
if transactions.count() == 0:
premium_id = self.request.REQUEST.get('premium_id', None)
preapproval_amount = self.request.REQUEST.get('preapproval_amount', None)
premium_description = None
ack_name=''
ack_dedication=''
anonymous=''
else:
self.transaction = transactions[0]
# what stuff do we need to pull out to populate form?
# preapproval_amount, premium_id (which we don't have stored yet)
if self.transaction.premium is not None:
premium_id = self.transaction.premium.id
premium_description = self.transaction.premium.description
else:
premium_id = None
premium_description = None
preapproval_amount = self.transaction.amount
ack_name=self.transaction.ack_name
ack_dedication=self.transaction.ack_dedication
anonymous=self.transaction.anonymous
if premium_id is not None and preapproval_amount is None: if premium_id is not None and preapproval_amount is None:
try: try:
preapproval_amount = D(models.Premium.objects.get(id=premium_id).amount) preapproval_amount = D(models.Premium.objects.get(id=premium_id).amount)
except: except:
preapproval_amount = None preapproval_amount = None
self.data = {'preapproval_amount':preapproval_amount,
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id} 'premium_id':premium_id, 'premium_description':premium_description,
'ack_name':ack_name, 'ack_dedication':ack_dedication, 'anonymous':anonymous}
form_class = self.get_form_class() if self.request.method == 'POST':
self.data.update(self.request.POST.dict())
# no validation errors, please, when we're only doing a GET if self.request.method == 'POST' or premium_id:
# to avoid validation errors, don't bind the form return {'data':self.data}
if preapproval_amount is not None:
form = form_class(data)
else: else:
form = form_class() return {}
def get_context_data(self, **kwargs):
"""set up the pledge page"""
context = super(PledgeView, self).get_context_data(**kwargs)
try: try:
pubdate = work.publication_date[:4] pubdate = self.work.publication_date[:4]
except IndexError: except IndexError:
pubdate = 'unknown' pubdate = 'unknown'
context.update({ context.update({
'redirect_to_modify_pledge':False, 'work':self.work,
'work':work,'campaign':campaign, 'campaign':self.campaign,
'premiums':premiums, 'form':form, 'premiums':self.premiums,
'premium_id':premium_id, 'premium_id':self.data['premium_id'],
'faqmenu': 'pledge', 'faqmenu': 'modify' if self.transaction else 'pledge',
'pubdate':pubdate, 'pubdate':pubdate,
'payment_processor':settings.PAYMENT_PROCESSOR, 'transaction': self.transaction,
}) 'tid': self.transaction.id if self.transaction else None,
'premium_description': self.data['premium_description'],
'preapproval_amount':self.data['preapproval_amount'],
'payment_processor':self.transaction.host if self.transaction else None,
})
# check whether the user already has an ACTIVE transaction for the given campaign.
# if so, we should redirect the user to modify pledge page
# BUGBUG: but what about Completed Transactions?
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
if transactions.count() > 0:
context.update({'redirect_to_modify_pledge':True})
else:
context.update({'redirect_to_modify_pledge':False})
return context return context
def form_valid(self, form): def get_premium(self,form):
work_id = self.kwargs["work_id"] premium_id = form.cleaned_data["premium_id"]
preapproval_amount = form.cleaned_data["preapproval_amount"] # confirm that the premium_id is a valid one for the campaign in question
anonymous = form.cleaned_data["anonymous"] try:
ack_name = form.cleaned_data["ack_name"] premium = models.Premium.objects.get(id=premium_id)
ack_link = form.cleaned_data["ack_link"] if not (premium.campaign is None or premium.campaign == self.campaign):
ack_dedication = form.cleaned_data["ack_dedication"] premium = None
except models.Premium.DoesNotExist, e:
premium = None
return premium
def form_valid(self, form):
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval # right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
campaign = models.Work.objects.get(id=int(work_id)).last_campaign()
premium_id = form.cleaned_data["premium_id"] p = PaymentManager()
# confirm that the premium_id is a valid one for the campaign in question if self.transaction:
try: # modifying the transaction...
premium = models.Premium.objects.get(id=premium_id) assert self.transaction.type == PAYMENT_TYPE_AUTHORIZATION and self.transaction.status == TRANSACTION_STATUS_ACTIVE
if not (premium.campaign is None or premium.campaign == campaign): status, url = p.modify_transaction(self.transaction, form.cleaned_data["preapproval_amount"],
premium = None premium=self.get_premium(form),
except models.Premium.DoesNotExist, e: paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
premium = None ack_name=form.cleaned_data["ack_name"],
ack_dedication=form.cleaned_data["ack_dedication"],
p = PaymentManager(embedded=self.embedded) anonymous=form.cleaned_data["anonymous"],
)
# PledgeView is wrapped in login_required -- so in theory, user should never be None -- but I'll keep this logic here for now. logger.info("status: {0}, url:{1}".format(status, url))
if self.request.user.is_authenticated():
user = self.request.user if status and url is not None:
logger.info("PledgeView (Modify): " + url)
return HttpResponseRedirect(url)
elif status and url is None:
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_complete'), self.transaction.id))
else:
return HttpResponse("No modification made")
else: else:
user = None t, url = p.process_transaction('USD', form.cleaned_data["preapproval_amount"],
host = None,
if not self.embedded: campaign=self.campaign,
user=self.request.user,
return_url = None premium=premium,
nevermind_url = None paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
ack_name=form.cleaned_data["ack_name"],
# the recipients of this authorization is not specified here but rather by the PaymentManager. ack_dedication=form.cleaned_data["ack_dedication"],
# set the expiry date based on the campaign deadline anonymous=form.cleaned_data["anonymous"],
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) )
if url:
paymentReason = "Unglue.it Pledge for {0}".format(campaign.name) logger.info("PledgeView url: " + url)
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, preapproval_amount, expiry=expiry, campaign=campaign, list=None, user=user, return HttpResponseRedirect(url)
return_url=return_url, nevermind_url=nevermind_url, anonymous=anonymous, premium=premium, else:
paymentReason=paymentReason, ack_name=ack_name, ack_link=ack_link, ack_dedication=ack_dedication) logger.error("Attempt to produce transaction id {0} failed".format(t.id))
else: # embedded view -- which we're not actively using right now. return HttpResponse("Our attempt to enable your transaction failed. We have logged this error.")
# embedded view triggerws instant payment: send to the partnering RH
receiver_list = [{'email':settings.PAYPAL_NONPROFIT_PARTNER_EMAIL, 'amount':preapproval_amount}]
return_url = None
nevermind_url = None
t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user,
return_url=return_url, nevermind_url=nevermind_url, anonymous=anonymous, premium=premium,
ack_name=ack_name, ack_link=ack_link, ack_dedication=ack_dedication)
if url:
logger.info("PledgeView url: " + url)
return HttpResponseRedirect(url)
else:
logger.error("Attempt to produce transaction id {0} failed".format(t.id))
return HttpResponse("Our attempt to enable your transaction failed. We have logged this error.")
class PledgeModifyView(FormView):
"""
A view to handle request to change an existing pledge
"""
template_name="pledge.html"
form_class = CampaignPledgeForm
embedded = False
def get_context_data(self, **kwargs):
context = super(PledgeModifyView, self).get_context_data(**kwargs)
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
assert self.request.user.is_authenticated()
user = self.request.user
work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
try:
campaign = work.last_campaign()
premiums = campaign.custom_premiums() | models.Premium.objects.filter(id=150)
# which combination of campaign and transaction status required?
# Campaign must be ACTIVE
assert campaign.status == 'ACTIVE'
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
assert transactions.count() == 1
transaction = transactions[0]
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
except Exception, e:
raise e
# what stuff do we need to pull out to populate form?
# preapproval_amount, premium_id (which we don't have stored yet)
if transaction.premium is not None:
premium_id = transaction.premium.id
premium_description = transaction.premium.description
else:
premium_id = None
premium_description = None
# is there a Transaction for an ACTIVE campaign for this
# should make sure Transaction is modifiable.
preapproval_amount = transaction.amount
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id}
# initialize form with the current state of the transaction if the current values empty
form = kwargs['form']
if not(form.is_bound):
form_class = self.get_form_class()
form = form_class(initial=data)
context.update({
'work':work,
'campaign':campaign,
'premiums':premiums,
'form':form,
'preapproval_amount':preapproval_amount,
'premium_id':premium_id,
'premium_description': premium_description,
'faqmenu': 'modify',
'tid': transaction.id,
'payment_processor':settings.PAYMENT_PROCESSOR,
'transaction': transaction,
})
return context
def form_invalid(self, form):
logger.info("form.non_field_errors: {0}".format(form.non_field_errors()))
response = self.render_to_response(self.get_context_data(form=form))
return response
def form_valid(self, form):
# What are the situations we need to deal with?
# 2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
# if new amount is greater than max_amount...need to go out and get new approval.
# to start with, we can use the standard pledge_complete, pledge_cancel machinery
# might have to modify the pledge_complete, pledge_cancel because the messages are going to be
# different because we're modifying a pledge rather than a new one.
work_id = self.kwargs["work_id"]
preapproval_amount = form.cleaned_data["preapproval_amount"]
ack_name = form.cleaned_data["ack_name"]
ack_link = form.cleaned_data["ack_link"]
ack_dedication = form.cleaned_data["ack_dedication"]
anonymous = form.cleaned_data["anonymous"]
assert self.request.user.is_authenticated()
user = self.request.user
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
campaign = models.Work.objects.get(id=int(work_id)).last_campaign()
assert campaign.status == 'ACTIVE'
premium_id = form.cleaned_data["premium_id"]
# confirm that the premium_id is a valid one for the campaign in question
try:
premium = models.Premium.objects.get(id=premium_id)
if not (premium.campaign is None or premium.campaign == campaign):
premium = None
except models.Premium.DoesNotExist, e:
premium = None
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
assert transactions.count() == 1
transaction = transactions[0]
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
p = PaymentManager(embedded=self.embedded)
paymentReason = "Unglue.it Pledge for {0}".format(campaign.name)
status, url = p.modify_transaction(transaction=transaction, amount=preapproval_amount, premium=premium,
paymentReason=paymentReason, ack_name=ack_name, ack_link=ack_link,
ack_dedication=ack_dedication, anonymous=anonymous)
logger.info("status: {0}, url:{1}".format(status, url))
if status and url is not None:
logger.info("PledgeModifyView paypal: " + url)
return HttpResponseRedirect(url)
elif status and url is None:
# let's use the pledge_complete template for now and maybe look into customizing it.
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_complete'), transaction.id))
else:
return HttpResponse("No modification made")
class PledgeRechargeView(TemplateView): class PledgeRechargeView(TemplateView):
@ -869,7 +731,7 @@ class PledgeRechargeView(TemplateView):
context = super(PledgeRechargeView, self).get_context_data(**kwargs) context = super(PledgeRechargeView, self).get_context_data(**kwargs)
# the following should be true since PledgeModifyView.as_view is wrapped in login_required # the following should be true since PledgeView.as_view is wrapped in login_required
assert self.request.user.is_authenticated() assert self.request.user.is_authenticated()
user = self.request.user user = self.request.user
@ -888,18 +750,15 @@ class PledgeRechargeView(TemplateView):
if transaction is not None: if transaction is not None:
# the recipients of this authorization is not specified here but rather by the PaymentManager. # the recipients of this authorization is not specified here but rather by the PaymentManager.
# set the expiry date based on the campaign deadline
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
ack_name = transaction.ack_name ack_name = transaction.ack_name
ack_link = transaction.ack_link
ack_dedication = transaction.ack_dedication ack_dedication = transaction.ack_dedication
paymentReason = "Unglue.it Recharge for {0}".format(campaign.name) paymentReason = "Unglue.it Recharge for {0}".format(campaign.name)
p = PaymentManager(embedded=False) p = PaymentManager()
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, transaction.amount, expiry=expiry, campaign=campaign, list=None, user=user, t, url = p.authorize('USD', transaction.amount, campaign=campaign, list=None, user=user,
return_url=return_url, nevermind_url=nevermind_url, anonymous=transaction.anonymous, premium=transaction.premium, return_url=return_url, anonymous=transaction.anonymous, premium=transaction.premium,
paymentReason=paymentReason, ack_name=ack_name, ack_link=ack_link, ack_dedication=ack_dedication) paymentReason=paymentReason, ack_name=ack_name, ack_dedication=ack_dedication)
logger.info("Recharge url: {0}".format(url)) logger.info("Recharge url: {0}".format(url))
else: else:
url = None url = None
@ -1090,73 +949,11 @@ class PledgeCancelView(FormView):
except Exception, e: except Exception, e:
logger.error("Exception from attempt to cancel pledge for campaign id {0} for username {1}: {2}".format(campaign_id, user.username, e)) logger.error("Exception from attempt to cancel pledge for campaign id {0} for username {1}: {2}".format(campaign_id, user.username, e))
return HttpResponse("Sorry, something went wrong in canceling your campaign pledge. We have logged this error.") return HttpResponse("Sorry, something went wrong in canceling your campaign pledge. We have logged this error.")
class PledgeNeverMindView(TemplateView):
"""A callback for PayPal to tell unglue.it that a payment transaction has been canceled by the user"""
template_name="pledge_nevermind.html"
def get_context_data(self):
context = super(PledgeNeverMindView, self).get_context_data()
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
# pull out the transaction id and try to get the corresponding Transaction
transaction_id = self.request.REQUEST.get("tid")
transaction = Transaction.objects.get(id=transaction_id)
# work and campaign in question
try:
campaign = transaction.campaign
work = campaign.work
except Exception, e:
campaign = None
work = None
# we need to check whether the user tied to the transaction is indeed the authenticated user.
correct_user = False
try:
if user.id == transaction.user.id:
correct_user = True
except Exception, e:
pass
# check that the user had not already approved the transaction
# do we need to first run PreapprovalDetails to check on the status
# is it of type=PAYMENT_TYPE_AUTHORIZATION and status is NONE or ACTIVE (but approved is false)
if transaction.type == PAYMENT_TYPE_AUTHORIZATION:
correct_transaction_type = True
else:
correct_transaction_type = False
# status?
# give the user an opportunity to approved the transaction again
# provide a URL to click on.
# https://www.sandbox.paypal.com/?cmd=_ap-preapproval&preapprovalkey=PA-6JV656290V840615H
try_again_url = '%s?cmd=_ap-preapproval&preapprovalkey=%s' % (settings.PAYPAL_PAYMENT_HOST, transaction.preapproval_key)
context["transaction"] = transaction
context["correct_user"] = correct_user
context["correct_transaction_type"] = correct_transaction_type
context["try_again_url"] = try_again_url
context["work"] = work
context["campaign"] = campaign
context["faqmenu"] = "cancel"
return context
class DonateView(FormView): class DonateView(FormView):
template_name="donate.html" template_name="donate.html"
form_class = DonateForm form_class = DonateForm
embedded = False
#def get_context_data(self, **kwargs): #def get_context_data(self, **kwargs):
# context = super(DonateView, self).get_context_data(**kwargs) # context = super(DonateView, self).get_context_data(**kwargs)
@ -1173,7 +970,7 @@ class DonateView(FormView):
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval # right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
campaign = None campaign = None
p = PaymentManager(embedded=self.embedded) p = PaymentManager()
# we should force login at this point -- or if no account, account creation, login, and return to this spot # we should force login at this point -- or if no account, account creation, login, and return to this spot
if self.request.user.is_authenticated(): if self.request.user.is_authenticated():
@ -1187,8 +984,8 @@ class DonateView(FormView):
#redirect the page back to campaign page on success #redirect the page back to campaign page on success
return_url = self.request.build_absolute_uri(reverse('donate')) return_url = self.request.build_absolute_uri(reverse('donate'))
t, url = p.pledge('USD', TARGET_TYPE_DONATION, receiver_list, campaign=campaign, list=None, user=user, t, url = p.pledge('USD', receiver_list, campaign=campaign, list=None, user=user,
return_url=return_url, anonymous=anonymous, ack_name=ack_name, ack_link=ack_link, return_url=return_url, anonymous=anonymous, ack_name=ack_name,
ack_dedication=ack_dedication) ack_dedication=ack_dedication)
if url: if url:

56
payment/credit.py Normal file
View File

@ -0,0 +1,56 @@
from datetime import timedelta
from django.contrib.auth.models import User
from django.conf import settings
from regluit.payment.parameters import *
from regluit.utils.localdatetime import now
from regluit.payment.baseprocessor import BasePaymentRequest
def pledge_transaction(t,user,amount):
"""commit <amount> from a <user>'s credit to a specified transaction <t>"""
if t.amount and t.host == PAYMENT_HOST_CREDIT:
#changing the pledge_transaction
user.credit.add_to_pledged(amount-t.amount)
else:
user.credit.add_to_pledged(amount)
t.amount=amount
t.max_amount=amount
t.host = PAYMENT_HOST_CREDIT
t.type = PAYMENT_TYPE_AUTHORIZATION
t.status=TRANSACTION_STATUS_ACTIVE
t.approved=True
now_val = now()
t.date_authorized = now_val
t.date_expired = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
t.save()
class CancelPreapproval(BasePaymentRequest):
'''
Cancels an exisiting token.
'''
def __init__(self, transaction):
self.transaction = transaction
if transaction.user.credit.add_to_pledged(-transaction.amount):
#success
transaction.status=TRANSACTION_STATUS_CANCELED
transaction.save()
else:
self.errorMessage="couldn't cancel the transaction"
self.status = 'Credit Cancel Failure'
class PreapprovalDetails(BasePaymentRequest):
status = None
approved = None
currency = None
amount = None
def __init__(self, transaction):
self.status = transaction.status
self.approved = transaction.approved
self.currency = transaction.currency
self.amount = transaction.amount

View File

@ -5,6 +5,9 @@ from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from regluit.payment.parameters import * from regluit.payment.parameters import *
from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created
from regluit.payment import credit
from regluit.payment.baseprocessor import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
import uuid import uuid
import traceback import traceback
@ -14,6 +17,8 @@ import logging
from decimal import Decimal as D from decimal import Decimal as D
from xml.dom import minidom from xml.dom import minidom
import urllib, urlparse import urllib, urlparse
from datetime import timedelta
from django.conf import settings from django.conf import settings
@ -531,58 +536,28 @@ class PaymentManager( object ):
logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string()) logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
return False return False
def authorize(self, currency, target, amount, expiry=None, campaign=None, list=None, user=None, def authorize(self, transaction, expiry= None, return_url=None, paymentReason="unglue.it Pledge", modification=False):
return_url=None, nevermind_url=None, anonymous=False, premium=None,
paymentReason="unglue.it Pledge", ack_name=None, ack_link=None, ack_dedication=None,
modification=False):
''' '''
authorize authorize
authorizes a set amount of money to be collected at a later date authorizes a set amount of money to be collected at a later date
currency: a 3-letter paypal currency code, i.e. USD
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
amount: the amount to authorize
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
list: optional list object(to be set with TARGET_TYPE_LIST)
user: optional user object
return_url: url to redirect supporter to after a successful PayPal transaction return_url: url to redirect supporter to after a successful PayPal transaction
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
anonymous: whether this pledge is anonymous
premium: the premium selected by the supporter for this transaction
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
modification: whether this authorize call is part of a modification of an existing pledge modification: whether this authorize call is part of a modification of an existing pledge
ack_name, ack_link, ack_dedication: how the user will be credited in the unglued ebook, if applicable
return value: a tuple of the new transaction object and a re-direct url. If the process fails, return value: a tuple of the new transaction object and a re-direct url. If the process fails,
the redirect url will be None the redirect url will be None
''' '''
t = Transaction.objects.create(amount=amount,
max_amount=amount,
type=PAYMENT_TYPE_AUTHORIZATION,
execution = EXECUTE_TYPE_CHAINED_DELAYED,
target=target,
currency=currency,
status='NONE',
campaign=campaign,
list=list,
user=user,
anonymous=anonymous,
premium=premium,
ack_name=ack_name,
ack_link=ack_link,
ack_dedication=ack_dedication
)
# we might want to not allow for a return_url or nevermind_url to be passed in but calculated if host==None:
#TODO send user to select a payment processor
pass
# we might want to not allow for a return_url to be passed in but calculated
# here because we have immediate access to the Transaction object. # here because we have immediate access to the Transaction object.
if nevermind_url is None:
nevermind_path = "{0}?{1}".format(reverse('pledge_nevermind'),
urllib.urlencode({'tid':t.id}))
nevermind_url = urlparse.urljoin(settings.BASE_URL, nevermind_path)
if return_url is None: if return_url is None:
return_path = "{0}?{1}".format(reverse('pledge_complete'), return_path = "{0}?{1}".format(reverse('pledge_complete'),
@ -590,7 +565,7 @@ class PaymentManager( object ):
return_url = urlparse.urljoin(settings.BASE_URL, return_path) return_url = urlparse.urljoin(settings.BASE_URL, return_path)
method = getattr(t.get_payment_class(), "Preapproval") method = getattr(t.get_payment_class(), "Preapproval")
p = method(t, amount, expiry, return_url=return_url, nevermind_url=nevermind_url, paymentReason=paymentReason) p = method(transaction, transaction.max_amount, expiry, return_url=return_url, paymentReason=paymentReason)
# Create a response for this # Create a response for this
envelope = p.envelope() envelope = p.envelope()
@ -600,11 +575,11 @@ class PaymentManager( object ):
correlation_id = p.correlation_id(), correlation_id = p.correlation_id(),
timestamp = p.timestamp(), timestamp = p.timestamp(),
info = p.raw_response, info = p.raw_response,
transaction=t) transaction=transaction)
if p.success() and not p.error(): if p.success() and not p.error():
t.preapproval_key = p.key() transaction.preapproval_key = p.key()
t.save() transaction.save()
url = p.next_url() url = p.next_url()
@ -624,16 +599,69 @@ class PaymentManager( object ):
# send the notice here for now # send the notice here for now
# this is actually premature since we're only about to send the user off to the payment system to # this is actually premature since we're only about to send the user off to the payment system to
# authorize a charge # authorize a charge
pledge_created.send(sender=self, transaction=t) pledge_created.send(sender=self, transaction=transaction)
return t, url return transaction, url
else: else:
t.error = p.error_string() transaction.error = p.error_string()
t.save() transaction.save()
logger.info("Authorize Error: " + p.error_string()) logger.info("Authorize Error: " + p.error_string())
return transaction, None
def process_transaction(self, currency, amount, host=None, campaign=None, user=None,
return_url=None, anonymous=False, premium=None,
paymentReason="unglue.it Pledge", ack_name=None, ack_dedication=None,
modification=False):
'''
process
saves and processes a proposed transaction; decides if the transaction should be processed immediately.
currency: a 3-letter currency code, i.e. USD
amount: the amount to authorize
host: the name of the processing module; if none, send user back to decide!
campaign: required campaign object
user: optional user object
return_url: url to redirect supporter to after a successful transaction
anonymous: whether this pledge is anonymous
premium: the premium selected by the supporter for this transaction
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
modification: whether this authorize call is part of a modification of an existing pledge
ack_name, ack_dedication: how the user will be credited in the unglued ebook, if applicable
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
the redirect url will be None
'''
# set the expiry date based on the campaign deadline
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
t = Transaction.objects.create(amount=0,
max_amount=amount,
currency=currency,
status='NONE',
campaign=campaign,
user=user,
anonymous=anonymous,
premium=premium,
ack_name=ack_name,
ack_dedication=ack_dedication
)
# does user have enough credit to pledge now?
if user.credit.available >= amount :
# YES!
credit.pledge_transaction(t,user,amount)
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':t.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
pledge_created.send(sender=self, transaction=t)
return t, return_url
else:
#TODO send user to choose payment path
return t, None return t, None
def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None): def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None):
''' '''
@ -677,26 +705,29 @@ class PaymentManager( object ):
return canceled return canceled
def modify_transaction(self, transaction, amount, expiry=None, anonymous=None, premium=None, def modify_transaction(self, transaction, amount, expiry=None, premium=None,
return_url=None, nevermind_url=None, paymentReason=None, return_url=None, nevermind_url=None, paymentReason=None,
ack_name=None, ack_link=None, ack_dedication=None): ack_name=None, ack_dedication=None, anonymous=None):
''' '''
modify modify
Modifies a transaction. The only type of modification allowed is to the amount and expiration date Modifies a transaction.
2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
if new amount is greater than max_amount...need to go out and get new approval.
to start with, we can use the standard pledge_complete, pledge_cancel machinery
might have to modify the pledge_complete, pledge_cancel because the messages are going to be
different because we're modifying a pledge rather than a new one.
amount: the new amount amount: the new amount
expiry: the new expiration date, or if none the current expiration date will be used expiry: the new expiration date, or if none the current expiration date will be used
anonymous: new anonymous value; if None, then keep old value anonymous: new anonymous value; if None, then keep old value
premium: new premium selected; if None, then keep old value premium: new premium selected; if None, then keep old value
return_url: the return URL after the preapproval(if needed) return_url: the return URL after the preapproval(if needed)
nevermind_url: the cancel url after the preapproval(if needed)
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed
''' '''
# Can only modify the amount of a preapproval for now
if transaction.type != PAYMENT_TYPE_AUTHORIZATION: if transaction.type != PAYMENT_TYPE_AUTHORIZATION:
logger.info("Error, attempt to modify an invalid transaction type") logger.info("Error, attempt to modify an invalid transaction type")
return False, None return False, None
@ -707,34 +738,50 @@ class PaymentManager( object ):
logger.info("Error, attempt to modify a transaction that is not active") logger.info("Error, attempt to modify a transaction that is not active")
return False, None return False, None
# if any of expiry, anonymous, or premium is None, use the existing value if transaction.host == PAYMENT_HOST_CREDIT:
if expiry is None: # does user have enough credit to pledge now?
expiry = transaction.date_expired if transaction.user.credit.available >= amount-transaction.amount :
if anonymous is None: # YES!
anonymous = transaction.anonymous transaction.anonymous = anonymous
if premium is None: transaction.premium = premium
premium = transaction.premium transaction.ack_name = ack_name
transaction.ack_dedication = ack_dedication
if amount > transaction.max_amount or expiry != transaction.date_expired: credit.pledge_transaction(transaction,transaction.user,amount)
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
# Start a new authorization for the new amount logger.info("Updated amount of transaction to %f" % amount)
pledge_modified.send(sender=self, transaction=transaction,up_or_down="decreased" if amount-transaction.amount<0 else "increased")
return transaction, return_url
else:
#TODO send user to choose payment path
return t, None
elif amount > transaction.max_amount or expiry != transaction.date_expired:
# set the expiry date based on the campaign deadline
expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
# Start a new transaction for the new amount
t = Transaction.objects.create(amount=amount,
max_amount=amount,
host=transaction.host,
currency=currency,
status=TRANSACTION_STATUS_CREATED,
campaign=transaction.campaign,
user=transaction.user,
anonymous=anonymous if anonymous!=None else transaction.anonymous,
premium=premium if premium != None else transaction.premium,
ack_name=ack_name,
ack_dedication=ack_dedication
)
t, url = self.authorize(transaction.currency, t, url = self.authorize(transaction,
transaction.target, expiry=expiry if expiry else transaction.date_expired,
amount, return_url=return_url,
expiry, paymentReason=paymentReason,
transaction.campaign, modification=True
transaction.list, )
transaction.user,
return_url,
nevermind_url,
anonymous,
premium,
paymentReason,
ack_name,
ack_link,
ack_dedication,
True)
if t and url: if t and url:
# Need to re-direct to approve the transaction # Need to re-direct to approve the transaction
@ -762,7 +809,6 @@ class PaymentManager( object ):
transaction.anonymous = anonymous transaction.anonymous = anonymous
transaction.premium = premium transaction.premium = premium
transaction.ack_name = ack_name transaction.ack_name = ack_name
transaction.ack_link = ack_link
transaction.ack_dedication = ack_dedication transaction.ack_dedication = ack_dedication
transaction.save() transaction.save()
@ -820,16 +866,15 @@ class PaymentManager( object ):
logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string()) logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
return False return False
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None, def pledge(self, currency, receiver_list, campaign=None, user=None,
return_url=None, nevermind_url=None, anonymous=False, premium=None, ack_name=None, return_url=None, nevermind_url=None, anonymous=False, premium=None, ack_name=None,
ack_link=None, ack_dedication=None): ack_dedication=None):
''' '''
pledge pledge
Performs an instant payment Performs an instant payment
currency: a 3-letter paypal currency code, i.e. USD currency: a 3-letter paypal currency code, i.e. USD
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
receiver_list: a list of receivers for the transaction, in this format: receiver_list: a list of receivers for the transaction, in this format:
[ [
@ -837,8 +882,7 @@ class PaymentManager( object ):
{'email':'email-2', 'amount':amount2} {'email':'email-2', 'amount':amount2}
] ]
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN) campaign: required campaign object
list: optional list object(to be set with TARGET_TYPE_LIST)
user: optional user object user: optional user object
return_url: url to redirect supporter to after a successful PayPal transaction return_url: url to redirect supporter to after a successful PayPal transaction
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
@ -863,13 +907,11 @@ class PaymentManager( object ):
currency=currency, currency=currency,
status='NONE', status='NONE',
campaign=campaign, campaign=campaign,
list=list,
user=user, user=user,
date_payment=now(), date_payment=now(),
anonymous=anonymous, anonymous=anonymous,
premium=premium, premium=premium,
ack_name=ack_name, ack_name=ack_name,
ack_link=ack_link,
ack_dedication=ack_dedication ack_dedication=ack_dedication
) )

View File

@ -0,0 +1,224 @@
# encoding: 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 model 'Credit'
db.create_table('payment_credit', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='credit', unique=True, to=orm['auth.User'])),
('balance', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
('pledged', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
('last_activity', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
))
db.send_create_signal('payment', ['Credit'])
# Adding model 'CreditLog'
db.create_table('payment_creditlog', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
('amount', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('action', self.gf('django.db.models.fields.CharField')(max_length=16)),
))
db.send_create_signal('payment', ['CreditLog'])
# Deleting field 'Transaction.target'
db.delete_column('payment_transaction', 'target')
# Deleting field 'Transaction.list'
db.delete_column('payment_transaction', 'list_id')
# Adding field 'Transaction.ack_name'
db.add_column('payment_transaction', 'ack_name', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
# Adding field 'Transaction.ack_dedication'
db.add_column('payment_transaction', 'ack_dedication', self.gf('django.db.models.fields.CharField')(max_length=140, null=True), keep_default=False)
def backwards(self, orm):
# Deleting model 'Credit'
db.delete_table('payment_credit')
# Deleting model 'CreditLog'
db.delete_table('payment_creditlog')
# Adding field 'Transaction.target'
db.add_column('payment_transaction', 'target', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)
# Adding field 'Transaction.list'
db.add_column('payment_transaction', 'list', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['core.Wishlist'], null=True), keep_default=False)
# Deleting field 'Transaction.ack_name'
db.delete_column('payment_transaction', 'ack_name')
# Deleting field 'Transaction.ack_dedication'
db.delete_column('payment_transaction', 'ack_dedication')
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(2012, 8, 31, 2, 10, 24, 467332)'}),
'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(2012, 8, 31, 2, 10, 24, 467190)'}),
'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', [], {}),
'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'}),
'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'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'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'}),
'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.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']

View File

@ -4,8 +4,11 @@ from django.conf import settings
from regluit.core.models import Campaign, Wishlist, Premium from regluit.core.models import Campaign, Wishlist, Premium
from regluit.payment.parameters import * from regluit.payment.parameters import *
from regluit.payment.signals import credit_balance_added from regluit.payment.signals import credit_balance_added
from decimal import Decimal from decimal import Decimal, NaN
import uuid import uuid
import urllib
import logging
logger = logging.getLogger(__name__)
class Transaction(models.Model): class Transaction(models.Model):
@ -14,16 +17,13 @@ class Transaction(models.Model):
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=settings.PAYMENT_PROCESSOR, max_length=32, null=False) host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
# target: e.g, TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST -- defined in parameters.py
target = models.IntegerField(default=TARGET_TYPE_NONE, 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='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)
@ -75,14 +75,14 @@ class Transaction(models.Model):
# how to acknowledge the user on the supporter page of the campaign ebook # how to acknowledge the user on the supporter page of the campaign ebook
ack_name = models.CharField(max_length=64, null=True) ack_name = models.CharField(max_length=64, null=True)
ack_link = models.URLField(null=True)
ack_dedication = models.CharField(max_length=140, null=True) ack_dedication = models.CharField(max_length=140, null=True)
# whether the user wants to be not listed publicly # whether the user wants to be not listed publicly
anonymous = models.BooleanField(null=False) anonymous = models.BooleanField(null=False)
# list: makes allowance for pledging against a Wishlist: not currently in use @property
list = models.ForeignKey(Wishlist, null=True) def ack_link(self):
return 'https://unglue.it/supporter/%s'%urllib.urlencode(self.user.username)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.secret: if not self.secret:
@ -149,10 +149,17 @@ class Receiver(models.Model):
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):
# a write only record of Donation Credit Transactions
user = models.ForeignKey(User, null=True)
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
timestamp = models.DateTimeField(auto_now=True)
action = models.CharField(max_length=16)
class Credit(models.Model): class Credit(models.Model):
user = models.OneToOneField(User, related_name='credit') user = models.OneToOneField(User, related_name='credit')
balance = models.IntegerField(default=0) balance = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
pledged = models.IntegerField(default=0) 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
@ -165,17 +172,29 @@ class Credit(models.Model):
else: else:
self.balance = self.balance + num_credits self.balance = self.balance + num_credits
self.save() self.save()
credit_balance_added.send(sender=self, amount=num_credits) try: # bad things can happen here if you don't return True
CreditLog(user = self.user, amount = num_credits, action="add_to_balance").save()
except:
logger.exception("failed to log add_to_balance of %s", num_credits)
try:
credit_balance_added.send(sender=self, amount=num_credits)
except:
logger.exception("credit_balance_added failed of %s", num_credits)
return True return True
def add_to_pledged(self, num_credits): def add_to_pledged(self, num_credits):
if not isinstance( num_credits, int): num_credits=Decimal(num_credits)
if num_credits is NaN:
return False return False
if self.balance - self.pledged < num_credits : if self.balance - self.pledged < num_credits :
return False return False
else: 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
CreditLog(user = self.user, amount = num_credits, action="add_to_pledged").save()
except:
logger.exception("failed to log add_to_pledged of %s", num_credits)
return True return True
def use_pledge(self, num_credits): def use_pledge(self, num_credits):
@ -187,6 +206,10 @@ class Credit(models.Model):
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:
CreditLog(user = self.user, amount = - num_credits, action="use_pledge").save()
except:
logger.exception("failed to log use_pledge of %s", num_credits)
return True return True
def transfer_to(self, receiver, num_credits): def transfer_to(self, receiver, num_credits):

View File

@ -5,17 +5,15 @@ PAYMENT_TYPE_AUTHORIZATION = 2
PAYMENT_HOST_NONE = "none" PAYMENT_HOST_NONE = "none"
PAYMENT_HOST_PAYPAL = "paypal" PAYMENT_HOST_PAYPAL = "paypal"
PAYMENT_HOST_AMAZON = "amazon" PAYMENT_HOST_AMAZON = "amazon"
PAYMENT_HOST_TEST = "test"
PAYMENT_HOST_CREDIT = "credit"
PAYMENT_HOST_STRIPE = "stripe"
EXECUTE_TYPE_NONE = 0 EXECUTE_TYPE_NONE = 0
EXECUTE_TYPE_CHAINED_INSTANT = 1 EXECUTE_TYPE_CHAINED_INSTANT = 1
EXECUTE_TYPE_CHAINED_DELAYED = 2 EXECUTE_TYPE_CHAINED_DELAYED = 2
EXECUTE_TYPE_PARALLEL = 3 EXECUTE_TYPE_PARALLEL = 3
TARGET_TYPE_NONE = 0
TARGET_TYPE_CAMPAIGN = 1
TARGET_TYPE_LIST = 2
TARGET_TYPE_DONATION = 3
# The default status for a transaction that is newly created # The default status for a transaction that is newly created
TRANSACTION_STATUS_NONE = 'None' TRANSACTION_STATUS_NONE = 'None'
@ -47,6 +45,3 @@ TRANSACTION_STATUS_REFUNDED = 'Refunded'
# The transaction was refused/denied # The transaction was refused/denied
TRANSACTION_STATUS_FAILED = 'Failed' TRANSACTION_STATUS_FAILED = 'Failed'
# these two following parameters are probably extraneous since I think we will compute dynamically where to return each time.
COMPLETE_URL = '/paymentcomplete'
NEVERMIND_URL = '/paymentnevermind'

View File

@ -190,7 +190,7 @@ class PledgeTest(TestCase):
# Note, set this to 1-5 different receivers with absolute amounts for each # Note, set this to 1-5 different receivers with absolute amounts for each
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00}] receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00}]
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None) t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
self.validateRedirect(t, url, 1) self.validateRedirect(t, url, 1)
@ -220,7 +220,7 @@ class PledgeTest(TestCase):
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00}, receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00},
{'email':settings.PAYPAL_TEST_RH_EMAIL, 'amount':10.00}] {'email':settings.PAYPAL_TEST_RH_EMAIL, 'amount':10.00}]
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None) t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
self.validateRedirect(t, url, 2) self.validateRedirect(t, url, 2)
@ -244,7 +244,7 @@ class PledgeTest(TestCase):
# Note, set this to 1-5 different receivers with absolute amounts for each # Note, set this to 1-5 different receivers with absolute amounts for each
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':50000.00}] receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':50000.00}]
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None) t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
self.validateRedirect(t, url, 1) self.validateRedirect(t, url, 1)
@ -284,7 +284,7 @@ class AuthorizeTest(TestCase):
# Note, set this to 1-5 different receivers with absolute amounts for each # Note, set this to 1-5 different receivers with absolute amounts for each
t, url = p.authorize('USD', TARGET_TYPE_NONE, 100.0, campaign=None, list=None, user=None) t, url = p.authorize('USD', 100.0, campaign=None, list=None, user=None)
self.validateRedirect(t, url) self.validateRedirect(t, url)
@ -388,7 +388,7 @@ class BasicGuiTest(TestCase):
def suite(): def suite():
#testcases = [PledgeTest, AuthorizeTest, TransactionTest] #testcases = [PledgeTest, AuthorizeTest, TransactionTest]
testcases = [TransactionTest] testcases = [TransactionTest, CreditTest]
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases]) suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
return suites return suites

View File

@ -17,8 +17,6 @@ if settings.DEBUG:
url(r"^testexecute", "testExecute"), url(r"^testexecute", "testExecute"),
url(r"^testcancel", "testCancel"), url(r"^testcancel", "testCancel"),
url(r"^querycampaign", "queryCampaign"), url(r"^querycampaign", "queryCampaign"),
url(r"^runtests", "runTests"),
url(r"^paymentcomplete","paymentcomplete"),
url(r"^checkstatus", "checkStatus"), url(r"^checkstatus", "checkStatus"),
url(r"^testfinish", "testFinish"), url(r"^testfinish", "testFinish"),
url(r"^testrefund", "testRefund"), url(r"^testrefund", "testRefund"),

View File

@ -13,7 +13,8 @@ from django.test.utils import setup_test_environment
from django.template import RequestContext from django.template import RequestContext
from unittest import TestResult from unittest import TestResult
from regluit.payment.tests import PledgeTest, AuthorizeTest
import uuid import uuid
from decimal import Decimal as D from decimal import Decimal as D
@ -112,12 +113,8 @@ def testAuthorize(request):
receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00}, receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00},
{'email': TEST_RECEIVERS[1], 'amount':10.00}] {'email': TEST_RECEIVERS[1], 'amount':10.00}]
if campaign_id: campaign = Campaign.objects.get(id=int(campaign_id))
campaign = Campaign.objects.get(id=int(campaign_id)) t, url = p.authorize('USD', amount, campaign=campaign, return_url=None, list=None, user=None)
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, amount, campaign=campaign, return_url=None, list=None, user=None)
else:
t, url = p.authorize('USD', TARGET_TYPE_NONE, amount, campaign=None, return_url=None, list=None, user=None)
if url: if url:
logger.info("testAuthorize: " + url) logger.info("testAuthorize: " + url)
@ -248,12 +245,9 @@ def testPledge(request):
else: else:
receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}] receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}]
if campaign_id: campaign = Campaign.objects.get(id=int(campaign_id))
campaign = Campaign.objects.get(id=int(campaign_id)) t, url = p.pledge('USD', receiver_list, campaign=campaign, list=None, user=user, return_url=None)
t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user, return_url=None)
else:
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=user, return_url=None)
if url: if url:
logger.info("testPledge: " + url) logger.info("testPledge: " + url)
@ -264,33 +258,6 @@ def testPledge(request):
logger.info("testPledge: Error " + str(t.error)) logger.info("testPledge: Error " + str(t.error))
return HttpResponse(response) return HttpResponse(response)
def runTests(request):
try:
# Setup the test environement. We need to run these tests on a live server
# so our code can receive IPN notifications from paypal
setup_test_environment()
result = TestResult()
# Run the authorize test
test = AuthorizeTest('test_authorize')
test.run(result)
# Run the pledge test
test = PledgeTest('test_pledge_single_receiver')
test.run(result)
# Run the pledge failure test
test = PledgeTest('test_pledge_too_much')
test.run(result)
output = "Tests Run: " + str(result.testsRun) + str(result.errors) + str(result.failures)
logger.info(output)
return HttpResponse(output)
except:
traceback.print_exc()
@csrf_exempt @csrf_exempt
def handleIPN(request, module): def handleIPN(request, module):
@ -303,11 +270,6 @@ def handleIPN(request, module):
return HttpResponse("ipn") return HttpResponse("ipn")
def paymentcomplete(request):
# pick up all get and post parameters and display
output = "payment complete"
output += request.method + "\n" + str(request.REQUEST.items())
return HttpResponse(output)
def checkStatus(request): def checkStatus(request):
# Check the status of all PAY transactions and flag any errors # Check the status of all PAY transactions and flag any errors