Merge branch 'relaunch'

pull/1/head
eric 2012-09-26 15:01:39 -04:00
commit 2260437cfc
64 changed files with 4296 additions and 2756 deletions

View File

@ -82,5 +82,24 @@
"description": "Your username, profile URL, and profile tagline under \"bibliophiles\"",
"created": "2011-11-17 22:03:37"
}
},
{
"pk": 150,
"model": "core.premium",
"fields": {
"campaign": null,
"amount": 0,
"type": "00",
"description": "No premium, thanks! I just want to help unglue.",
"created": "2011-11-17 22:03:37"
}
},
{
"pk": 1,
"model": "core.badge",
"fields": {
"name": "pledger",
"description": "has made a pledge in at least one ungluing campaign"
}
}
]

View File

@ -0,0 +1,249 @@
# 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'
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, 9, 17, 15, 54, 11, 910150)'}),
'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, 9, 17, 15, 54, 11, 910015)'}),
'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, 9, 17, 15, 54, 11, 494504)', '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'}),
'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'}),
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.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

@ -111,6 +111,16 @@ class Premium(models.Model):
t_model=get_model('payment','Transaction')
return self.limit - t_model.objects.filter(premium=self).count()
class PledgeExtra:
premium=None
anonymous=False
ack_name=''
ack_dedication=''
def __init__(self,premium=None,anonymous=False,ack_name='',ack_dedication=''):
self.premium=premium
self.anonymous=anonymous
self.ack_name=ack_name
self.ack_dedication=ack_dedication
class CampaignAction(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
@ -131,8 +141,8 @@ class CCLicense():
)
CHOICES = CCCHOICES+(('PD-US', 'Public Domain, US'),)
@classmethod
def url(klass, license):
@staticmethod
def url(license):
if license == 'PD-US':
return 'http://creativecommons.org/publicdomain/mark/1.0/'
elif license == 'CC0':
@ -152,8 +162,8 @@ class CCLicense():
else:
return ''
@classmethod
def badge(klass,license):
@staticmethod
def badge(license):
if license == 'PD-US':
return 'https://i.creativecommons.org/p/mark/1.0/88x31.png'
elif license == 'CC0':
@ -264,7 +274,12 @@ class Campaign(models.Model):
def transactions(self, **kwargs):
p = PaymentManager()
return p.query_campaign(self, summary=False, campaign_total=True, **kwargs)
# handle default parameter values
kw = {'summary':False, 'campaign_total':True}
kw.update(kwargs)
return p.query_campaign(self, **kw)
def activate(self):
@ -368,6 +383,35 @@ class Campaign(models.Model):
return ungluers
def ungluer_transactions(self):
"""
returns a list of authorized transactions for campaigns in progress,
or completed transactions for successful campaigns
used to build the acks page -- because ack_name, _link, _dedication adhere to transactions,
it's easier to return transactions than ungluers
"""
p = PaymentManager()
ungluers={"all":[],"supporters":[], "patrons":[], "bibliophiles":[]}
anons = 0
if self.status == "ACTIVE":
translist = p.query_campaign(self, summary=False, pledged=True, authorized=True)
elif self.status == "SUCCESSFUL":
translist = p.query_campaign(self, summary=False, pledged=True, completed=True)
else:
translist = []
for transaction in translist:
ungluers['all'].append(transaction.user)
if transaction.anonymous:
anons += 1
if transaction.amount >= Premium.TIERS["bibliophile"]:
ungluers['bibliophiles'].append(transaction)
elif transaction.amount >= Premium.TIERS["patron"]:
ungluers['patrons'].append(transaction)
elif transaction.amount >= Premium.TIERS["supporter"]:
ungluers['supporters'].append(transaction)
return ungluers
def effective_premiums(self):
"""returns the available premiums for the Campaign including any default premiums"""
q = Q(campaign=self) | Q(campaign__isnull=True)
@ -375,7 +419,7 @@ class Campaign(models.Model):
def custom_premiums(self):
"""returns only the active custom premiums for the Campaign"""
return Premium.objects.filter(campaign=self).filter(type='CU')
return Premium.objects.filter(campaign=self).filter(type='CU').order_by('amount')
@property
def rightsholder(self):
@ -428,8 +472,8 @@ class Identifier(models.Model):
other.delete()
return identifier
@classmethod
def get_or_add(klass, type='goog', value=None, edition=None, work=None):
@staticmethod
def get_or_add( type='goog', value=None, edition=None, work=None):
try:
return Identifier.objects.get(type=type, value=value)
except Identifier.DoesNotExist:
@ -652,6 +696,13 @@ class Work(models.Model):
return edition.publication_date
return ''
@property
def publication_date_year(self):
try:
return self.publication_date[:4]
except IndexError:
return 'unknown'
def __unicode__(self):
return self.title
@ -752,8 +803,8 @@ class Edition(models.Model):
except IndexError:
return ''
@classmethod
def get_by_isbn(klass, isbn):
@staticmethod
def get_by_isbn( isbn):
if len(isbn)==10:
isbn=regluit.core.isbn.convert_10_to_13(isbn)
try:
@ -791,8 +842,8 @@ class Ebook(models.Model):
return CCLicense.badge('PD-US')
return CCLicense.badge(self.rights)
@classmethod
def infer_provider(klass, url):
@staticmethod
def infer_provider( url):
if not url:
return None
# provider derived from url. returns provider value. remember to call save() afterward
@ -859,6 +910,7 @@ class UserProfile(models.Model):
twitter_id = models.CharField(max_length=15, blank=True)
facebook_id = models.PositiveIntegerField(null=True)
librarything_id = models.CharField(max_length=31, blank=True)
badges = models.ManyToManyField('Badge', related_name='holders')
goodreads_user_id = models.CharField(max_length=32, null=True, blank=True)
goodreads_user_name = models.CharField(max_length=200, null=True, blank=True)
@ -866,6 +918,10 @@ class UserProfile(models.Model):
goodreads_auth_secret = models.TextField(null=True, blank=True)
goodreads_user_link = models.CharField(max_length=200, null=True, blank=True)
class Badge(models.Model):
name = models.CharField(max_length=72, blank=True)
description = models.TextField(default='', null=True)
#class CampaignSurveyResponse(models.Model):
# # generic
# campaign = models.ForeignKey("Campaign", related_name="surveyresponse", null=False)

View File

@ -174,6 +174,7 @@ def handle_pledge_modified(sender, transaction=None, up_or_down=None, **kwargs):
# we need to know if pledges were modified up or down because Amazon handles the
# transactions in different ways, resulting in different user-visible behavior;
# we need to set expectations appropriately
# up_or_down is 'increased', 'decreased', or 'canceled'
if transaction==None or up_or_down==None:
return
notification.queue([transaction.user], "pledge_status_change", {

View File

@ -13,7 +13,7 @@ from decimal import Decimal as D
from selectable.forms import AutoCompleteSelectMultipleWidget,AutoCompleteSelectMultipleField
from selectable.forms import AutoCompleteSelectWidget,AutoCompleteSelectField
from regluit.core.models import UserProfile, RightsHolder, Claim, Campaign, Premium, Ebook, Edition
from regluit.core.models import UserProfile, RightsHolder, Claim, Campaign, Premium, Ebook, Edition, PledgeExtra
from regluit.core.lookups import OwnerLookup
from regluit.utils.localdatetime import now
@ -187,6 +187,25 @@ class OpenCampaignForm(forms.ModelForm):
fields = 'name', 'work', 'managers'
widgets = { 'work': forms.HiddenInput }
def getTransferCreditForm(maximum, data=None, *args, **kwargs ):
class TransferCreditForm(forms.Form):
recipient = AutoCompleteSelectField(
OwnerLookup,
label='Recipient',
widget=AutoCompleteSelectWidget(OwnerLookup),
required=True,
error_messages={'required': 'Please ensure the recipient is a valid Unglue.it account.'},
)
amount = forms.IntegerField(
required=True,
min_value=1,
max_value=maximum,
label="Transfer Amount",
error_messages={'min_value': 'Transfer amount must be positive', 'max_value': 'You only have %(limit_value)s available for transfer'},
)
return TransferCreditForm( data=data )
class EditManagersForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField(
OwnerLookup,
@ -285,58 +304,70 @@ class CampaignPledgeForm(forms.Form):
decimal_places=2,
label="Pledge Amount",
)
anonymous = forms.BooleanField(required=False, label=_("Don't display my username in the supporters list"))
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_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:"))
premium_id = forms.IntegerField(required=False)
premium=None
@property
def pledge_extra(self):
return PledgeExtra( anonymous=self.cleaned_data['anonymous'],
ack_name=self.cleaned_data['ack_name'],
ack_dedication=self.cleaned_data['ack_dedication'],
premium=self.premium)
def clean_preapproval_amount(self):
data = self.cleaned_data['preapproval_amount']
if data is None:
preapproval_amount = self.cleaned_data['preapproval_amount']
if preapproval_amount is None:
raise forms.ValidationError(_("Please enter a pledge amount."))
return data
return preapproval_amount
# should we do validation on the premium_id here?
# can see whether it corresponds to a real premium -- do that here?
# can also figure out moreover whether it's one of the allowed premiums for that campaign....
def clean(self):
cleaned_data = self.cleaned_data
# check on whether the preapproval amount is < amount for premium tier. If so, put an error message
def clean_premium_id(self):
premium_id = self.cleaned_data['premium_id']
try:
preapproval_amount = cleaned_data.get("preapproval_amount")
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:
premium= Premium.objects.get(id=premium_id)
if premium.limit>0:
if premium.limit<=premium.premium_count:
self.premium= Premium.objects.get(id=premium_id)
if self.premium.limit>0:
if self.premium.limit<=self.premium.premium_count:
raise forms.ValidationError(_("Sorry, that premium is fully subscribed."))
except Premium.DoesNotExist:
raise forms.ValidationError(_("Sorry, that premium is not valid."))
def clean(self):
# check on whether the preapproval amount is < amount for premium tier. If so, put an error message
try:
preapproval_amount = self.cleaned_data.get("preapproval_amount")
logger.info("preapproval_amount: {0}, premium_id: {1}, premium_amount:{2}".format(preapproval_amount, self.premium.id, self.premium.amount))
if preapproval_amount < self.premium.amount:
logger.info("raising form validating error")
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (self.premium.amount)))
except Exception, e:
if isinstance(e, forms.ValidationError):
raise e
return cleaned_data
return self.cleaned_data
class DonateForm(forms.Form):
donation_amount = forms.DecimalField(
class CCForm(forms.Form):
username = forms.CharField(max_length=30, required=True )
work_id = forms.IntegerField(required=False, widget=forms.HiddenInput() )
stripe_token = forms.CharField(required=False, widget=forms.HiddenInput())
preapproval_amount= forms.DecimalField(
required=False,
min_value=D('1.00'),
max_value=D('100000.00'),
decimal_places=2,
label="Donation",
label="Pledge",
)
anonymous = forms.BooleanField(required=False, label=_("Don't display my username in the donors' list"))
retain_cc_info = forms.BooleanField(required=False, initial=True, label=_("Keep my credit card on record"))
class DonateForm(forms.Form):
preapproval_amount = forms.DecimalField( widget=forms.HiddenInput() )
username = forms.CharField(max_length=30, required=True, widget=forms.HiddenInput() )
work_id = forms.IntegerField(required=False, widget=forms.HiddenInput() )
title = forms.CharField(max_length=30, required=False, widget=forms.HiddenInput() )
def clean(self):
cleaned_data = self.cleaned_data
return cleaned_data
class GoodreadsShelfLoadingForm(forms.Form):
goodreads_shelf_name_number = forms.CharField(widget=forms.Select(choices=(

View File

@ -2,22 +2,6 @@
{% load humanize %}
{% url privacy as privacyurl %}
{% block extra_head %}
<script src ='https://www.paypalobjects.com/js/external/dg.js' type='text/javascript'></script>
{% if embedded %}
<script>
//
// This page is also used as the completion URL for purchases. Close the lightbox
// whenever we are opened
//
dgFlow = top.dgFlow || top.opener.top.dgFlow;
dgFlow.closeFlow();
top.close()
</script>
{% endif %}
{% endblock %}
{% block content %}
@ -52,13 +36,5 @@
<p>No associated transactions</p>
{% endif %}
{% if embedded %}
<script>
//
// This has to be included AFTER the definition of the button
//
var dgFlow = new PAYPAL.apps.DGFlow({ trigger: 'pledgeBtn' });
</script>
{% endif %}
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends "basedocumentation.html" %}
{% block title %}Donate{% endblock %}
{% block extra_extra_head %}
{% endblock %}
{% block doccontent %}
<h2>Donate</h2>
<p>Wonderful: We're glad that you would like to donate to our <b>partnering non-profit</b>.</p>
<form method="post" action="#">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Donate" />
</form>
{% endblock %}

View File

@ -0,0 +1,61 @@
{% extends "basedocumentation.html" %}
{% load humanize %}
{% block title %}Donations{% endblock %}
{% block extra_extra_head %}
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/ui-lightness/jquery-ui.css" type="text/css" media="screen">
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
{{ transfer_form.media.css }}
<script type="text/javascript" src="{{ jquery_home }}"></script>
<script type="text/javascript" src="{{ jquery_ui_home }}" ></script>
{{ transfer_form.media.js }}
{% endblock %}
{% block doccontent %}
<h2>Donation Credits</h2>
<p>
You have a balance of {{ user.credit.balance }} donation credits. <br />
You have pledged {{ user.credit.pledged }} donation credits to ungluing campaigns.<br />
You have {{ user.credit.available }} donation credits available to pledge or transfer.<br />
</p>
<div class="clearfix">
<h2>Donation Credit Transfers</h2>
{% if transfer_message %}
<p>{{ transfer_message }}
{% if transfer_amount %}
<br /> Recipient: <a href="{% url supporter recipient %}">{{ recipient }}</a>
<br /> Amount: {{ transfer_amount }} donation credits
{% endif %}
</p>
{% endif %}
<p>
You may transfer up to {{ user.credit.available }} donation credits to another Unglue.it user.<br />
</p>
<form action="#" method="POST">
{% csrf_token %}
{{ transfer_form.as_p }}
<input id="transfer_submit" type="submit" name="transfer" value="Transfer Credits" />
</form>
</div>
<div class="clearfix">
<h2>About Donation Credits</h2>
{% if nonprofit.is_on %}
<p>Unglue.it uses donation credits to cooperate with a non-profit charity, {{ nonprofit.name }} in the ungluing of books. When you make a donation to {{ nonprofit.name }}, you can get credits for your donation. Then you can direct how {{ nonprofit.name }} uses your donation in support of ungluing campaigns. You can also transfer your credits to other ungluers. To make a donation, and receive credits, click the button!
</p>
<div id="donate_charity">
<form method="GET" action="{{nonprofit.link}}">
{{ donate_form.non_field_errors }}
{{donate_form}}
<input name="donate_submit" type="submit" value="Go Donate!" id="donate_submit" />
</form>
</div>
</div>
{% else %}
<p>Donation credits are not turned on yet.</p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,45 @@
{% extends "basepledge.html" %}
{% load humanize %}
{% block title %}You Have Donation Credits{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
{% endblock %}
{% block doccontent %}
<div style="height:15px"></div>
<div class="jsmodule rounded clearfix">
<div class="jsmod-content">
<div>
<h2> Donation Credited </h2>
<p>{% if error %}
Your donation 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 %}
Congratulations, your donation 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 %}
</p>
<!-- sent log: {{ envelope.sent }} -->
<!-- error: {{ error }} -->
<!-- {{ transaction }} -->
<div>
<h2> Your donation credits </h2>
<p>
You have a balance of {{ request.user.credit.balance }} donation credits. <br />
You have pledged {{ request.user.credit.pledged }} donation credits to ungluing campaigns.<br />
You have {{ request.user.credit.available }} donation credits available to pledge or <a href="{% url donation %}">transfer</a>.<br />
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,29 @@
{% extends "basepledge.html" %}
{% load humanize %}
{% block title %}Please Log in as...{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
{% endblock %}
{% block doccontent %}
<div style="height:15px"></div>
<div class="jsmodule rounded clearfix">
<div class="jsmod-content">
<div><h2>Wrong user for donation credit</h2>
<div>
<p>Unglue.it would like to process your donation credit, but you are currently logged in as <code>{{request.user.username}}</code>. Your donation credit from {{nonprofit.name}} for ${{ envelope.amount }}.{{ envelope.cents }} is designated for <code>{{ envelope.username }}</code>. Do 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 auth_login %}?next={{ request.get_full_path|urlencode }}'>log in</a> as <code>{{ envelope.username }}</code>. If you have any problem, don't hesitate to contact <a href="mailto:support@gluejar.com?subject={{ request.get_full_path|urlencode }}">unglue.it support</a>.
</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -6,21 +6,28 @@
<li class="first parent">
<span class="faq">How do I pledge?</span>
<span class="menu level2 answer">
Enter your pledge amount and select a premium. (You may select a premium at any level up to and including the amount you pledge.) After you click Pledge, you'll be directed through Amazon to complete the transaction.
Enter your pledge amount and select a premium. (You may select a premium at any level up to and including the amount you pledge.) If you pledge enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. After you click Pledge, you'll be directed through Amazon to complete the transaction.
</span>
</li>
<li class="parent">
<span class="faq">Do I need an Amazon account to pledge?</span>
<span class="menu level2 answer">
No. While you can use yours if you have one, any major credit card will do.
Yes. We know this is an obstacle for some users, and we apologize. Unfortunately we cannot offer other options at this time. There are more details at <a href="http://blog.unglue.it/2012/05/03/unglue-it-payment-options-amazon-vs-paypal/">our blog</a>.
</span>
</li>
<li class="parent">
<span class="faq">Can I use PayPal or some other non-Amazon payment system?</span>
<span class="menu level2 answer">
We're sorry, but no. At this time PayPal and Amazon are the only payment processors which support pledges (rather than immediate charges). While we're working on adding PayPal support, we don't yet have all the approvals we need to do so. There are more details at <a href="http://blog.unglue.it/2012/05/03/unglue-it-payment-options-amazon-vs-paypal/">our blog</a>.
</span>
</li>
<li class="parent">
<span class="faq">When will I be charged?</span>
<span class="menu level2 answer">
If this campaign reaches its target price, you'll be charged at that time. If it does not, your pledge will expire on {{ campaign.deadline }} (Eastern US time) and you will not be charged.
Campaigns succeed if they reach their target price by their deadline. If this campaign succeeds, you'll be charged within a day of when it reaches its target. If it does not, your pledge will expire on {{ campaign.deadline }} (Eastern US time) and you will not be charged.
</span>
</li>

View File

@ -1,5 +1,5 @@
{% extends "basedocumentation.html" %}
{% block title %}Open Access eBooks{% endblock %}
{% block title %}Ungluers supporting the {{ campaign }}{% endblock %}
{% block extra_extra_head %}
<link rel="stylesheet" type="text/css" href="/static/css/vanilla.css" class="day" title="day" />
{% endblock %}
@ -9,43 +9,45 @@
<div class="agate-info">
<p>© {{ campaign.work.preferred_edition.publication_date }} by {{ campaign.work.author }}</p>
<p>ISBN: {{ campaign.work.preferred_edition.isbn_13 }} .</p>
<p>URI: <a href="https://unglue.it/{{ campaign.work.id }}/">https://unglue.it/work/{{ campaign.work.id }}/</a> (this work).</p>
<p>URI: <a href="https://unglue.it/work/{{ campaign.work.id }}/">https://unglue.it/work/{{ campaign.work.id }}/</a> (this work).</p>
<p><img src="images/unglueitlogo.png" alt="unglue.it logo" /></p>
<p>
This <i>unglued</i> edition is distributed under the terms of the Creative Commons (<a href="{{ campaign.license_url }}">{{ campaign.license }}</a>) license.
The Creative Commons licensing is made possible by the support of readers like you.
Become an ungluer at <a href="https://unglue.it/">https://unglue.it/</a>
Become an ungluer at <a href="https://unglue.it/">https://unglue.it/</a> .
</p>
</div>
</section>
<section epub:type="other-credits" id="other-credits">
<h2>This is an unglued ebook</h2>
{% with campaign.ungluer_transactions as transactions %}
<p>
Unglued ebooks are made possible through the Unglue.it website by contributions from {{ campaign.ungluers.all|length }} readers like you.</p>
{% if campaign.ungluers.supporters %}
<p>Supporters of this edition include ungluers:
{% for ungluer in campaign.ungluers.supporters %}
{% if forloop.last %}and {% endif %}{{ ungluer.username }}{% if forloop.last %}. {% else %}, {% endif %}
Unglued ebooks are made possible through the Unglue.it website by contributions from {{ transactions.all|length }} readers like you.</p>
{% if transactions.supporters %}
<p>Supporters of this edition:
{% for transaction in transactions.supporters %}
{% if forloop.last %}{% if transactions.supporters|length > 1 %}and {% endif %}{% endif %}{{ transaction.ack_name }}{% if forloop.last %}. {% else %}, {% endif %}
{% endfor %}
</p>
{% endif %}
{% if campaign.ungluers.patrons %}
<p>Benefactors of this edition include ungluers:
{% for ungluer in campaign.ungluers.patrons %}
{% if forloop.last %}and {% endif %}<a href="{% if ungluer.profile.home_url %}{{ungluer.profile.home_url}}{% else %}https://unglue.it{% url supporter supporter_username=ungluer.username %}{% endif %}">{{ ungluer.username }}</a>{% if forloop.last %}. {% else %}, {% endif %}
{% if transactions.patrons %}
<p>Benefactors of this edition:
{% for transaction in transactions.patrons %}
{% if forloop.last %}{% if transactions.patrons|length > 1 %}and {% endif %}{% endif %}{% if transaction.ack_link %}<a href="{{transaction.ack_link}}">{{ transaction.ack_name }}</a>{% else %}{{ transaction.ack_name }}{% endif %}{% if forloop.last %}. {% else %}, {% endif %}
{% endfor %}
</p>
{% endif %}
{% if campaign.ungluers.bibliophiles %}
<p>Bibliophiles of this edition include ungluers:<dl>
{% for ungluer in campaign.ungluers.bibliophiles %}
<dt><a href="{% if ungluer.profile.home_url %}{{ungluer.profile.home_url}}{% else %}https://unglue.it{% url supporter supporter_username=ungluer.username %}{% endif %}">{{ ungluer.username }}</a></dt><dd>{{ ungluer.profile.tagline }}</dd>
{% if transactions.bibliophiles %}
<p>Bibliophiles of this edition:<dl>
{% for transaction in transactions.bibliophiles %}
<dt>{% if transaction.ack_link %}<a href="{{transaction.ack_link}}">{{ transaction.ack_name }}</a>{% else %}{{ transaction.ack_name }}{% endif %}</dt><dd>{{ transaction.ack_dedication }}</dd>
{% endfor %}
</dl>
</p>
{% endif %}
{% endwith %}
<p>
You can say thank you by supporting the ungluing of more books at <a href="https://unglue.it/">https://unglue.it/</a>
You can say thank you by supporting the ungluing of more books at <a href="https://unglue.it/">https://unglue.it/</a> .
</p>
</section>
<!-- trim here -->

View File

@ -0,0 +1,84 @@
{% extends "basepledge.html" %}
{% load humanize %}
{% block title %}Fund Your Pledge{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<link href="/static/stripe/tag.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/static/stripe/tag.js"></script>
{% endblock %}
{% block doccontent %}
<div style="height:15px"></div>
<div class="jsmodule rounded clearfix">
<div class="jsmod-content">
<div><h2>Funding Your Pledge</h2>
<div>We're so happy that you've decided to {% if modified %}increase your pledge{% else %}join{% endif %} this campaign.
{% if nonprofit.is_on %}
We have two ways we can fund your pledge of ${{preapproval_amount}}.
<ol>
<li>You can <a href="#donate">make a donation now</a> to {{nonprofit.name}}, a non-profit charity that's working with Unglue.it to give books to the world.</li>
<li>You can <a href="#authorize">give us your credit card</a> information now; we'll charge your card only if the campaign succeeds.</li>
</ol>
{% else %}
To fund your pledge of ${{preapproval_amount}}, you can <a href="#authorize">give us your credit card</a> information now; we'll charge your card only if the campaign succeeds.
{% endif %}
</div>
</div>
{% if nonprofit.is_on %}
<div id="donate" class="clearfix">
<h3>Donate to Charity</h3>
{% if request.user.credit.balance %}You have a donation credit of ${{ request.user.credit.balance }}. The amount that's uncommitted is ${{ request.user.credit.available }}, so you need to add at least ${{ needed }}.{% endif %}
<p> Click the button to be sent to the {{nonprofit.name}} donation site. When you complete your donation of at least ${{ needed }}, you'll be sent back to Unglue.it and your pledge will automatically be entered. You can move your donation credit to another campaign or campaigns at any time. {{nonprofit.name}} will hold all funds and earn any interest until a campaign you support succeeds.
</p>
<div id="donate_charity">
<form method="GET" action="{{nonprofit.link}}">
{{ donate_form.non_field_errors }}
{{donate_form}}
<input name="donate_submit" type="submit" value="Go Donate!" id="donate_submit" />
</form>
</div>
</div>
{% endif %}
<div id="authorize" class="clearfix">
<h3>Pledge by Credit Card</h3>
<p>Unglue.it uses Stripe to securely manage your Credit Card information.
</p>
{% if request.user.credit.available %}<p>Although you have ${{request.user.credit.available}} in donation credits, you can't support a campaign with a mixture of credit card pledges and donations.{% endif %}
<div id="cc_pledge">
<span class="payment-errors"></span>
<form action="" method="post" id="payment-form">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.as_p }}
<payment key="{{STRIPE_PK}}"></payment>
<input name="cc_submit" type="submit" value="Verify Credit Card" id="cc_submit" />
</form>
</div>
</div>
</div>
</div>
<script type="application/x-javascript">
var $j = jQuery.noConflict();
console.debug('setting up handlers in stripe.html');
$j('payment').bind('success.payment', function () {
console.debug('success.payment ev');
});
</script>
{% endblock %}

View File

@ -72,7 +72,7 @@ Please fix the following before launching your campaign:
<div>
<div class="pubinfo">
<h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ pubdate }}</h3>
<h3 class="book-year">{{ work.publication_date_year }}</h3>
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
{% extends "nonprofit_base.html" %}
{% load humanize %}
{% block title %}Donate to {{nonprofit.name}}{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<link href="/static/stripe/tag.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/static/stripe/tag.js"></script>
{% endblock %}
{% block doccontent %}
<div style="height:15px"></div>
<div class="jsmodule rounded clearfix">
<div class="jsmod-content">
<div><h1>Supporting <i>{{ get.title }}</i> through {{nonprofit.name}}</h1>
<div><p>{{nonprofit.name}} is cooperating with Unglue.it to make books free to the world. On this page, you can make a donation to us, and we'll hold the money until the ungluing campaign you've selected succeeds. Then we'll make a payment to the rightsholder along side unglue.it. Once you've made the donation, we'll ad donation credits to your unglue.it account, <code>{{get.username}}</code>.
</p>
<p>
To fund the pledge of ${{get.preapproval_amount|intcomma}} that you've made at unglue.it, you'll need to make a donation of at least that amount. You can donate more than that of course- you'll get additional donation credits to use on other campaigns. The larger your donation, the less percentage-wise that gets eaten up by processing fees.
</p>
<p>Any interest we earn will be used for our public purpose of helping libraries remain relevant into the future. If your unglue.it donation credits don't get used for 5 years, we'll use it in the same way. Your donation to {{nonprofit.name}} is deductible as a Charitable donation in the US.</p>
</div>
</div>
<div id="donate" class="clearfix">
<h2>Donate by Credit Card</h2>
<p>{{nonprofit.name}} uses Stripe to securely manage your Credit Card information.
</p>
<div id="cc_pledge">
<form method="POST" action="#">
{% csrf_token %}
{{ form.non_field_errors }}
{{form.as_p}}
<payment key="{{STRIPE_PK}}"></payment>
<input name="cc_submit" type="submit" value="Verify Credit Card" id="cc_submit" />
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,200 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
<head profile="http://gmpg.org/xfn/11">
<script type="text/javascript">var _sf_startpt=(new Date()).getTime()</script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{% block title %}Join the Movement | Library Renewal{% endblock %}</title>
<link href="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/stylesheets/blueprint/screen.css" media="screen, projection" rel="stylesheet" type="text/css" />
<link href="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/stylesheets/blueprint/print.css" media="print" rel="stylesheet" type="text/css" />
<!--[if IE]><link href="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/stylesheets/blueprint/ie.css" media="screen, projection" rel="stylesheet" type="text/css" /><![endif]-->
<link rel="stylesheet" href="http://libraryrenewal.org/wp-content/themes/library-renewal/style.css" type="text/css" media="screen" />
<link rel="alternate" type="application/rss+xml" title="Library Renewal RSS Feed" href="http://libraryrenewal.org/feed/" />
<link rel="alternate" type="application/atom+xml" title="Library Renewal Atom Feed" href="http://libraryrenewal.org/feed/atom/" />
<link rel="pingback" href="http://libraryrenewal.org/xmlrpc.php" />
<link rel='stylesheet' id='twitter-pro-css' href='http://libraryrenewal.org/wp-content/plugins/twitter-widget-premium/resources/style.css?ver=3.4.1' type='text/css' media='all' />
<link rel='stylesheet' id='sp-gallery-css' href='http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/sp-gallery.css?ver=3.4.1' type='text/css' media='all' />
<link rel='stylesheet' id='NextGEN-css' href='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/css/nggallery.css?ver=1.0.0' type='text/css' media='screen' />
<link rel='stylesheet' id='shutter-css' href='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/shutter/shutter-reloaded.css?ver=1.3.2' type='text/css' media='screen' />
<script type='text/javascript' src='http://libraryrenewal.org/wp-includes/js/jquery/jquery.js?ver=1.7.2'></script>
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/jquery.cycle.min.js?ver=2.86'></script>
<script type='text/javascript'>
/* <![CDATA[ */
var shutterSettings = {"msgLoading":"L O A D I N G","msgClose":"Click to Close","imageCount":"1"};
/* ]]> */
</script>
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/shutter/shutter-reloaded.js?ver=1.3.2'></script>
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/js/ngg.slideshow.min.js?ver=1.05'></script>
<script type='text/javascript' src='http://libraryrenewal.org/wp-includes/js/comment-reply.js?ver=3.4.1'></script>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://libraryrenewal.org/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://libraryrenewal.org/wp-includes/wlwmanifest.xml" />
<link rel='prev' title='About Us' href='http://libraryrenewal.org/about/' />
<link rel='next' title='Events/Research' href='http://libraryrenewal.org/events/' />
<meta name="generator" content="WordPress 3.4.1" />
<!-- All in One SEO Pack 1.6.13.3 by Michael Torbert of Semper Fi Web Design[275,324] -->
<link rel="canonical" href="http://libraryrenewal.org/join-the-movement/" />
<!-- /all in one seo pack -->
<!--[if lt IE 8]><link rel="stylesheet" href="http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/ie7.css" type="text/css" media="screen"><![endif]-->
<!--[if IE 8]><link rel="stylesheet" href="http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/ie8.css" type="text/css" media="screen"><![endif]-->
<link rel="stylesheet" href="http://libraryrenewal.org/wp-content/plugins/socialpop/socialpop-styles.css" type="text/css" media="screen" title="no title" charset="utf-8" />
<script type="text/javascript">
<!--
var sps_style = "popup";
var sps_box = "no";
var sps_boxfloat = "left";
var sps_title = "Share!";
var sps_notitle = "no";
var sps_holder = "gray";
var sps_bgcolor = "ffffff";
-->
</script>
<script src="http://libraryrenewal.org/wp-content/plugins/socialpop/socialpop.js" type="text/javascript" charset="utf-8"></script>
<script src="http://libraryrenewal.org/wp-content/plugins/socialpop/script.js" type="text/javascript" charset="utf-8"></script>
<meta name='NextGEN' content='1.8.2' />
{% block extra_extra_head %}{% endblock %}
</head>
<body class="page page-id-7 page-template-default" >
<div id="header" class="container">
<h2 id="logo"><a href="http://libraryrenewal.org">Library Renewal</a></h2>
<ul id="global-menu">
<li class="page_item page-item-2"><a href="http://libraryrenewal.org/about/" title="About Us">About Us</a></li>
<li class="page_item page-item-7"><a href="http://libraryrenewal.org/join-the-movement/" title="Join the Movement">Join</a></li>
<li class="page_item page-item-7"><a href="http://libraryrenewal.myshopify.com/" title="Store &amp; Donation Center">Store</a></li>
<li class="page_item page-item-9"><a href="http://libraryrenewal.org/events/" title="Events">Events</a></li>
<li class="page_item page-item-11"><a href="http://libraryrenewal.org/support/" title="Support">Support Us</a></li>
<li class="page_item page-item-53"><a href="http://libraryrenewal.org/blog/" title="Blog">Blog</a></li>
<li class="page_item page-item-13"><a href="http://libraryrenewal.org/contact/" title="Contact">Contact</a></li>
</ul>
</div>
<div id="content" class="container" style="border:none">
<div class="post" id="post-7">
<!-- SIDEBAR -->
<div id="sidebar" class="span-4 append-1">
<div class="callout">
<h3>Join the Movement</h3>
<p>We are making library renewal happen together. Give us feedback and interact with your colleagues to create the library access your community needs today.</p>
<a href="/join-the-movement" class="btn">Join the Movement</a>
</div>
<div class="callout">
<h3>Search the Site</h3>
<form method="get" action="http://www.libraryrenewal.org/?">
<input class="text" type="text" name="s" style="width:130px" >
<p class="clear clearfix"><button type="submit" class="button positive"><img alt="Search the Site" height="16" src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/folder_magnify.png" title="" width="16" />Search</button></p>
</form>
</div>
<div class="callout">
<h3>Library Renewal Posters</h3>
<a href="http://libraryrenewal.myshopify.com/collections/posters/"><img src="http://www.libraryrenewal.org/wp-content/uploads/2010/12/Cricket-Press-LR-sketches-2-197x300.jpg" style="width:150px"></a>
<a href="http://libraryrenewal.myshopify.com/collections/posters/" class="btn">Browse Posters</a>
</div>
</div>
<div class="span-19 last">
{% block doccontent %}{% endblock %}
<div id="lk-plugin"><div class="socialpop-container sps-solo">
<ul class="socialpop-list">
<li><a href="http://delicious.com/save?url={{ nonprofit.link|urlencode }}" onclick="window.open(this.href);return false;"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/delicious.png" width="32" height="32" alt="Delicious" title="Delicious" /></a></li>
<li><a href="http://digg.com/submit?url={{ nonprofit.link|urlencode }}" onclick="window.open(this.href);return false;"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/digg.png" width="32" height="32" alt="Digg" title="Digg" /></a></li>
<li><a href="mailto:?subject=Join the Movement&amp;body={{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/email.png" width="32" height="32" alt="E-Mail" title="E-Mail" /></a></li>
<li><a href="http://www.facebook.com/sharer.php?u={{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/facebook.png" width="32" height="32" alt="Facebook" title="Facebook" /></a></li>
<li><a href="http://www.google.com/buzz/post?url={{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/google-buzz.png" width="32" height="32" alt="Google Buzz" title="Google Buzz" /></a></li>
<li><a href="http://www.linkedin.com/shareArticle?mini=true&amp;url={{ nonprofit.link|urlencode }}&amp;message=Join+the+Movement"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/linkedin.png" width="32" height="32" alt="LinkedIn" title="LinkedIn" /></a></li>
<li><a href="http://www.stumbleupon.com/submit?url={{ nonprofit.link|urlencode }}&amp;title=Join+the+Movement"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/stumbleupon.png" width="32" height="32" alt="StumbleUpon" title="StumbleUpon" /></a></li>
<li><a href="http://twitter.com/home?status=Join+the+Movement+{{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/twitter.png" width="32" height="32" alt="Twitter" title="Twitter" /></a></li>
</ul>
</div></div>
</div>
</div>
</div>
<div id="footer" class="container">
<ul>
<li class="page_item page-item-2"><a href="http://libraryrenewal.org/about/" title="About Us">About Us</a></li>
<li class="page_item page-item-7"><a href="http://libraryrenewal.org/posters/" title="Join the Movement">Join the Movement</a></li>
<li class="page_item page-item-9"><a href="http://libraryrenewal.org/events/" title="Events">Events</a></li>
<li class="page_item page-item-11"><a href="http://libraryrenewal.org/support/" title="Support">Support Us</a></li>
<li class="page_item page-item-53"><a href="http://libraryrenewal.org/blog/" title="Blog">Blog</a></li>
<li class="page_item page-item-13"><a href="http://libraryrenewal.org/contact/" title="Contact">Contact</a></li>
</ul>
<p>Copyright &copy; 2010 Library Renewal | Built With Pride by <a href="http://www.commercekitchen.com" target="_new">www.commercekitchen.com</a></p>
</div>
<div id="share-container-outer">
<div id="share-container-inner">
<ul>
<li><a href="http://www.facebook.com/libraryrenewal" target="_new"><img src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/social-media-icons/facebook.png" width="16" height="16" alt="" title="" /></a></li>
<li><a href="http://twitter.com/libraryrenewal" target="_new"><img src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/social-media-icons/twitter.png" width="16" height="16" alt="" title="" /></a></li>
<li><a href="http://libraryrenewal.org/feed/" target="_new"><img src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/social-media-icons/feed.png" width="16" height="16" alt="" title="" /></a></li>
</ul>
</div>
</div>
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/sp-gallery.js?ver=3.4.1'></script>
</body>
</html>

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@
<div>
<div class="pubinfo">
<h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ pubdate }}</h3>
<h3 class="book-year">{{ work.publication_date_year }}</h3>
</div>
</div>
</div>
@ -52,8 +52,8 @@
{% if faqmenu == 'modify' %}
<div class="modify_notification clearfix"><h4>You've already pledged to this campaign:</h4>
<div>
Amount: ${{preapproval_amount|intcomma}}.<br />
Your premium: {% if premium_description %}{{ premium_description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
Amount: ${{transaction.amount|intcomma}}.<br />
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
</div>
<br /> You can modify your pledge below.
</div>
@ -68,11 +68,6 @@
{{ form.non_field_errors }}
<div class="pledge_amount">{{ form.preapproval_amount.label_tag }}: {{ form.preapproval_amount.errors }}${{ form.preapproval_amount }}</div>
{% comment %}
not supported yet; don't display
{{ form.anonymous.label_tag }}: {{ form.anonymous.errors }}{{ form.anonymous }}
{% endcomment %}
<div class="pledge_amount premium_level">Choose your premium:</div>
<ul class="support menu" id="premiums_list">
@ -81,9 +76,15 @@
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<label for="premium_{{premium_item.id}}">
<input type="radio" name="premium_id" id="premium_{{premium_item.id}}" value="{{premium_item.id}}" {% ifequal request.REQUEST.premium_id premium_item.id|stringformat:"s" %}checked="checked"{% else %} {% ifequal premium_id premium_item.id %}checked="checked"{% endifequal %}{% endifequal %} />
{% if premium_item.amount == 0 %}
<span class="menu-item-price">
Any amount
</span>
{% else %}
<span class="menu-item-price">
${{ premium_item.amount|intcomma }}
</span>
{% endif %}
<span class="menu-item-desc">
{{ premium_item.description }} {% ifnotequal premium_item.limit 0 %}<br /> Only {{ premium_item.premium_remaining }} remaining! {% endifnotequal %}
</span>
@ -92,6 +93,13 @@
{% endfor %}
</ul>
<div class="pledge_amount clearfix" id="mandatory_premiums">
<div>Depending on your pledge amount, you'll also get these acknowledgements.</div>
<div class="ack_active"><div class="ack_level">Any amount</div><div class="ack_header">The unglued ebook will be delivered to your inbox.</div></div>
<div id="ack_name" class="ack_inactive"><div class="ack_level">$25+</div><div class="ack_header">You'll be listed on the acknowledgements page of the unglued ebook<span id="ack_section"></span>.&nbsp;{{ form.ack_name.label_tag }} {{ form.ack_name.errors }}{{ form.ack_name }}<div id="anonbox"><I>{{ form.anonymous.label_tag }}</I> {{ form.anonymous.errors }}{{ form.anonymous }}</div></div></div>
<div id="ack_link" class="ack_inactive"><div class="ack_level">$50+</div><div class="ack_header">Your acknowledgement will link to your Unglue.it supporter page.{{ form.ack_link }}</div></div>
<div id="ack_dedication" class="ack_inactive"><div class="ack_level">$100+</div><div class="ack_header">Your acknowledgement can include a dedication (140 characters max).&nbsp;{{ form.ack_dedication.label_tag }} {{ form.ack_dedication.errors }}{{ form.ack_dedication }}</div></div>
</div>
<input name="pledge" type="submit" {% if faqmenu == 'modify' %}value="Modify Pledge"{% else %}value="Pledge Now"{% endif %} id="pledgesubmit" />
<input name="decoy" type="submit" id="fakepledgesubmit" disabled="disabled" />
{% comment %}
@ -100,30 +108,17 @@
</form>
</div>
</div>
<div class="cancel_notice">(You will be sent to <b>{{ payment_processor|capfirst }}</b>
{% if faqmenu == 'modify' %}, if needed, to modify your pledge. 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>{% else %} to complete your pledge.{% endif %}.)</div>
{% ifequal work.id 81724 %}
<!-- Google Code for OLA pledge page Conversion Page; allows for AdWords conversion tracking -->
<script type="text/javascript">
/* <![CDATA[ */
var google_conversion_id = 1072527387;
var google_conversion_language = "en";
var google_conversion_format = "3";
var google_conversion_color = "ffffff";
var google_conversion_label = "Vqw9CPi04AIQm_C1_wM";
var google_conversion_value = 0;
/* ]]> */
</script>
<script type="text/javascript" src="https://www.googleadservices.com/pagead/conversion.js">
</script>
<noscript>
<div style="display:inline;">
<img height="1" width="1" style="border-style:none;" alt="" src="https://www.googleadservices.com/pagead/conversion/1072527387/?value=0&amp;label=Vqw9CPi04AIQm_C1_wM&amp;guid=ON&amp;script=0"/>
</div>
</noscript>
{% endifequal %}
<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>
{% if transaction.ack_name %}
<div id="pass_ack_name" style="display: none;">{{ transaction.ack_name }}</div>
{% else %}
<div id="pass_ack_name" style="display: none;">{{ request.user.username }}</div>
{% endif %}
<div id="pass_ack_link" style="display: none;">{{ transaction.ack_link }}</div>
<div id="pass_ack_dedication" style="display: none;">{{ transaction.ack_dedication }}</div>
<div id="pass_anon" style="display: none;">{{ transaction.anonymous }}</div>
{% endblock %}

View File

@ -7,6 +7,8 @@
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<link type="text/css" rel="stylesheet" href="/static/css/searchandbrowse.css" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<script src="/static/js/slides.min.jquery.js"></script>
<script src="/static/js/slideshow.js"></script>
@ -29,7 +31,14 @@
<h2 class="thank-you">Thank you!</h2>
<p class="pledge_complete">You've just pledged ${{ transaction.amount|intcomma }} to <a href="{% url work work.id %}">{{ work.title }}</a>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy.</p>
<p class="pledge_complete">You've just {% if modified %}modified your pledge for{% else %}pledged{% endif %} ${{ transaction.amount|intcomma }} to <a href="{% url work work.id %}">{{ work.title }}</a>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy.</p>
<div class="modify_notification clearfix">
<div>
Amount: ${{transaction.amount|intcomma}}.<br />
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
</div>
</div>
<p class="pledge_complete">You can help even more by sharing this campaign with your friends:</p>
<div id="widgetcode">Copy/paste this into your site:<br /><textarea rows="7" cols="22">&lt;iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"&gt;&lt;/iframe&gt;</textarea></div>

View File

@ -130,7 +130,7 @@ $j(document).ready(function(){
<div>
<div class="pubinfo">
<h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ pubdate }}</h3>
<h3 class="book-year">{{ work.publication_date_year }}</h3>
</div>
{% if status == 'ACTIVE' %}
{% if pledged %}

View File

@ -3,12 +3,14 @@ from django.views.generic.simple import direct_to_template
from django.views.generic.base import TemplateView
from django.views.generic import ListView, DetailView
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from regluit.core.feeds import SupporterWishlistFeed
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 CampaignListView, DonateView, WorkListView, UngluedListView, InfoPageView, InfoLangView
from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeCancelView, PledgeRechargeView, FAQView
from regluit.frontend.views import CampaignListView, WorkListView, UngluedListView, InfoPageView, InfoLangView, DonationView, FundPledgeView
from regluit.frontend.views import NonprofitCampaign, DonationCredit, PledgeModifiedView
urlpatterns = patterns(
"regluit.frontend.views",
@ -53,12 +55,16 @@ urlpatterns = patterns(
url(r"^new_edition/(?P<work_id>)(?P<edition_id>)$", "new_edition", name="new_edition"),
url(r"^new_edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", "new_edition", name="new_edition"),
url(r"^googlebooks/(?P<googlebooks_id>.+)/$", "googlebooks", name="googlebooks"),
url(r"^donation/$", login_required(DonationView.as_view()), name="donation"),
url(r"^donation/credit/(?P<token>.+)/$", login_required(DonationCredit.as_view()), name="donation_credit"),
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/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(PledgeModifyView.as_view()), name="pledge_modify"),
url(r"^pledge/modified/$", login_required(PledgeModifiedView.as_view()), name="pledge_modified"),
url(r"^pledge/modify/(?P<work_id>\d+)$", login_required(PledgeView.as_view()), name="pledge_modify"),
url(r"^pledge/fund/(?P<t_id>\d+)$", login_required(FundPledgeView.as_view()), name="fund_pledge"),
url(r"^pledge/recharge/(?P<work_id>\d+)$", login_required(PledgeRechargeView.as_view()), name="pledge_recharge"),
url(r"^donate_to_campaign/$", csrf_exempt(NonprofitCampaign.as_view()), name="nonprofit"),
url(r"^subjects/$", "subjects", name="subjects"),
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),
url(r"^librarything/load/$","librarything_load", name="librarything_load"),
@ -85,6 +91,5 @@ if settings.DEBUG:
"regluit.frontend.views",
url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"),
url(r"^goodreads/clear_wishlist/$","clear_wishlist", name="clear_wishlist"),
url(r"^donate/$", DonateView.as_view(), name="donate"),
url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"),
)

View File

@ -17,6 +17,7 @@ import oauth2 as oauth
from django import forms
from django.conf import settings
from django.contrib.auth.models import User
from django.core import signing
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.contrib import messages
@ -47,16 +48,19 @@ from regluit.frontend.forms import UserData, UserEmail, ProfileForm, CampaignPle
from regluit.frontend.forms import RightsHolderForm, UserClaimForm, LibraryThingForm, OpenCampaignForm
from regluit.frontend.forms import getManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm, FeedbackForm
from regluit.frontend.forms import EbookForm, CustomPremiumForm, EditManagersForm, EditionForm, PledgeCancelForm
from regluit.frontend.forms import getTransferCreditForm, CCForm
from regluit.payment.manager import PaymentManager
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.paypal import Preapproval
from regluit.payment.models import Transaction, Account
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE, TRANSACTION_STATUS_NONE, TRANSACTION_STATUS_MODIFIED
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION, PAYMENT_TYPE_INSTANT
from regluit.payment.parameters import PAYMENT_HOST_STRIPE
from regluit.payment.credit import credit_transaction
from regluit.core import goodreads
from tastypie.models import ApiKey
from regluit.payment.models import Transaction
from regluit.payment.models import Transaction, Sent, CreditLog
from notification import models as notification
from regluit.payment import stripelib
logger = logging.getLogger(__name__)
@ -154,6 +158,7 @@ def work(request, work_id, action='display'):
except:
pledged = None
logger.info("pledged: {0}".format(pledged))
countdown = ""
try:
@ -185,11 +190,6 @@ def work(request, work_id, action='display'):
if action == 'preview':
work.last_campaign_status = 'ACTIVE'
try:
pubdate = work.publication_date[:4]
except IndexError:
pubdate = 'unknown'
if not request.user.is_anonymous():
claimform = UserClaimForm( request.user, data={'claim-work':work.pk, 'claim-user': request.user.id}, prefix = 'claim')
for edition in editions:
@ -210,8 +210,9 @@ def work(request, work_id, action='display'):
claimform = None
if campaign:
# pull up premiums explicitly tied to the campaign or generic premiums
premiums = campaign.effective_premiums()
# pull up premiums explicitly tied to the campaign
# mandatory premiums are only displayed in pledge process
premiums = campaign.custom_premiums()
else:
premiums = None
@ -242,7 +243,6 @@ def work(request, work_id, action='display'):
'wishers': wishers,
'base_url': base_url,
'editions': editions,
'pubdate': pubdate,
'pledged': pledged,
'activetab': activetab,
'alert': alert,
@ -417,11 +417,6 @@ def manage_campaign(request, id):
work = campaign.work
try:
pubdate = work.publication_date[:4]
except IndexError:
pubdate = 'unknown'
return render(request, 'manage_campaign.html', {
'campaign': campaign,
'form':form,
@ -429,7 +424,6 @@ def manage_campaign(request, id):
'alerts': alerts,
'premiums' : campaign.effective_premiums(),
'premium_form' : new_premium_form,
'pubdate': pubdate,
'work': work,
'activetab': activetab,
})
@ -573,139 +567,145 @@ class CampaignListView(FilterableListView):
context['facet'] =self.kwargs['facet']
return context
class DonationView(TemplateView):
template_name = "donation.html"
def get(self, request, *args, **kwargs):
context = self.get_context_data()
context['transfer_form']=getTransferCreditForm(self.request.user.credit.available)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
context = self.get_context_data()
transfer_form=getTransferCreditForm(self.request.user.credit.available, data=self.request.POST)
if transfer_form.is_valid():
if self.request.user.credit.transfer_to(transfer_form.cleaned_data['recipient'], transfer_form.cleaned_data['amount']):
#successful transfer
context['transfer_message'] = 'Your transfer has been successfully executed.'
context['recipient']= transfer_form.cleaned_data['recipient']
context['transfer_amount'] = transfer_form.cleaned_data['amount']
context['transfer_form']=getTransferCreditForm(self.request.user.credit.available)
else:
#unsuccessful transfer
context['transfer_message'] = 'Your transfer was not successful.'
context['transfer_form']=transfer_form
else:
#not valid
context['transfer_form']=transfer_form
return self.render_to_response(context)
def get_context_data(self, *args, **kwargs):
context = {'user' : self.request.user,'nonprofit': settings.NONPROFIT}
context['donate_form'] = DonateForm(initial={'username':self.request.user.username})
return context
class PledgeView(FormView):
template_name="pledge.html"
form_class = CampaignPledgeForm
embedded = False
transaction = None
campaign = None
work = None
premiums = None
data = None
def get(self, request, *args, **kwargs):
# 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()
user = self.request.user
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 campaign is None or campaign.status != 'ACTIVE':
raise Http404
premiums = campaign.effective_premiums()
premium_id = self.request.REQUEST.get('premium_id', None)
def get_preapproval_amount(self):
preapproval_amount = self.request.REQUEST.get('preapproval_amount', None)
if premium_id is not None and preapproval_amount is None:
if preapproval_amount:
return preapproval_amount
premium_id = self.request.REQUEST.get('premium_id', None)
if premium_id != None:
try:
preapproval_amount = D(models.Premium.objects.get(id=premium_id).amount)
except:
preapproval_amount = None
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id}
form_class = self.get_form_class()
# no validation errors, please, when we're only doing a GET
# to avoid validation errors, don't bind the form
if preapproval_amount is not None:
form = form_class(data)
if self.transaction:
if preapproval_amount:
preapproval_amount = preapproval_amount if preapproval_amount>self.transaction.amount else self.transaction.amount
else:
form = form_class()
preapproval_amount = self.transaction.amount
return preapproval_amount
def get_form_kwargs(self):
assert self.request.user.is_authenticated()
self.work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
# if there is no campaign or if campaign is not active, we should raise an error
try:
pubdate = work.publication_date[:4]
except IndexError:
pubdate = 'unknown'
self.campaign = self.work.last_campaign()
# TODO need to sort the premiums
self.premiums = self.campaign.custom_premiums() | models.Premium.objects.filter(id=150)
# Campaign must be ACTIVE
assert self.campaign.status == 'ACTIVE'
except Exception, e:
raise e
transactions = self.campaign.transactions().filter(user=self.request.user, status=TRANSACTION_STATUS_ACTIVE, type=PAYMENT_TYPE_AUTHORIZATION)
premium_id = self.request.REQUEST.get('premium_id', None)
if transactions.count() == 0:
ack_name=''
ack_dedication=''
anonymous=''
else:
self.transaction = transactions[0]
if premium_id == None and self.transaction.premium is not None:
premium_id = self.transaction.premium.id
ack_name=self.transaction.ack_name
ack_dedication=self.transaction.ack_dedication
anonymous=self.transaction.anonymous
self.data = {'preapproval_amount':self.get_preapproval_amount(), 'premium_id':premium_id,
'ack_name':ack_name, 'ack_dedication':ack_dedication, 'anonymous':anonymous}
if self.request.method == 'POST':
self.data.update(self.request.POST.dict())
return {'data':self.data}
else:
return {'initial':self.data}
def get_context_data(self, **kwargs):
"""set up the pledge page"""
context = super(PledgeView, self).get_context_data(**kwargs)
context.update({
'redirect_to_modify_pledge':False,
'work':work,'campaign':campaign,
'premiums':premiums, 'form':form,
'premium_id':premium_id,
'faqmenu': 'pledge',
'pubdate':pubdate,
'payment_processor':settings.PAYMENT_PROCESSOR,
'work':self.work,
'campaign':self.campaign,
'premiums':self.premiums,
'premium_id':self.data['premium_id'],
'faqmenu': 'modify' if self.transaction else 'pledge',
'transaction': self.transaction,
'tid': self.transaction.id 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
def form_valid(self, form):
work_id = self.kwargs["work_id"]
preapproval_amount = form.cleaned_data["preapproval_amount"]
anonymous = form.cleaned_data["anonymous"]
# 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"]
# 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
p = PaymentManager()
if self.transaction:
# modifying the transaction...
assert self.transaction.type == PAYMENT_TYPE_AUTHORIZATION and self.transaction.status == TRANSACTION_STATUS_ACTIVE
status, url = p.modify_transaction(self.transaction, form.cleaned_data["preapproval_amount"],
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
pledge_extra=form.pledge_extra
)
logger.info("status: {0}, url:{1}".format(status, url))
p = PaymentManager(embedded=self.embedded)
# PledgeView is wrapped in login_required -- so in theory, user should never be None -- but I'll keep this logic here for now.
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_modified'), self.transaction.id))
else:
user = None
if not self.embedded:
return_url = None
nevermind_url = None
# 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 )
paymentReason = "Unglue.it Pledge for {0}".format(campaign.name)
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, preapproval_amount, expiry=expiry, campaign=campaign, list=None, user=user,
return_url=return_url, nevermind_url=nevermind_url, anonymous=anonymous, premium=premium,
paymentReason=paymentReason)
else: # embedded view -- which we're not actively using right now.
# 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)
return HttpResponse("No modification made")
else:
t, url = p.process_transaction('USD', form.cleaned_data["preapproval_amount"],
host = None,
campaign=self.campaign,
user=self.request.user,
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
pledge_extra=form.pledge_extra
)
if url:
logger.info("PledgeView url: " + url)
return HttpResponseRedirect(url)
@ -713,133 +713,202 @@ class PledgeView(FormView):
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
"""
class FundPledgeView(FormView):
template_name="fund_the_pledge.html"
form_class = CCForm
transaction = None
template_name="pledge.html"
form_class = CampaignPledgeForm
embedded = False
def get_form_kwargs(self):
kwargs = super(FundPledgeView, self).get_form_kwargs()
assert self.request.user.is_authenticated()
if self.transaction is None:
self.transaction = get_object_or_404(Transaction, id=self.kwargs["t_id"])
if kwargs.has_key('data'):
data = kwargs['data'].copy()
else:
data = {}
data.update(
{'preapproval_amount':self.transaction.max_amount,
'username':self.request.user.username,
'work_id':self.transaction.campaign.work.id,
'title':self.transaction.campaign.work.title}
)
kwargs['data'] = data
return kwargs
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.effective_premiums()
# 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,
})
context = super(FundPledgeView, self).get_context_data(**kwargs)
context['modified'] = self.transaction.status==TRANSACTION_STATUS_MODIFIED
context['preapproval_amount']=self.transaction.max_amount
context['needed'] = self.transaction.max_amount - self.request.user.credit.available
context['transaction']=self.transaction
context['nonprofit'] = settings.NONPROFIT
context['STRIPE_PK'] = stripelib.STRIPE_PK
# note that get_form_kwargs() will already have been called once
donate_args=self.get_form_kwargs()
donate_args['data']['preapproval_amount']=context['needed']
context['donate_form'] = DonateForm(**donate_args)
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 post(self, request, *args, **kwargs):
logger.info('request.POST: {0}'.format(request.POST))
return super(FundPledgeView, self).post(request, *args, **kwargs)
def form_valid(self, form):
""" note desire to pledge; make sure there is a credit card to charge"""
# 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.
# first pass -- we have a token -- also do more direct coupling to stripelib -- then move to
# abstraction of payment.manager / payment.baseprocessor
work_id = self.kwargs["work_id"]
# demonstrate two possibilities: 1) token -> charge or 2) token->customer->charge
stripe_token = form.cleaned_data["stripe_token"]
preapproval_amount = form.cleaned_data["preapproval_amount"]
anonymous = form.cleaned_data["anonymous"]
retain_cc_info = form.cleaned_data["retain_cc_info"]
assert self.request.user.is_authenticated()
sc = stripelib.StripeClient()
# let's figure out what part of transaction can be used to store info
# try placing charge id in transaction.pay_key
# need to set amount
# how does max_amount get set? -- coming from /pledge/xxx/?
# max_amount is set -- but I don't think we need it for stripe
if retain_cc_info:
# create customer and charge id and then charge the customer
customer = sc.create_customer(card=stripe_token, description=self.request.user.username,
email=self.request.user.email)
account = Account(host = PAYMENT_HOST_STRIPE,
account_id = customer.id,
card_last4 = customer.active_card.last4,
card_type = customer.active_card.type,
card_exp_month = customer.active_card.exp_month,
card_exp_year = customer.active_card.exp_year,
card_fingerprint = customer.active_card.fingerprint,
card_country = customer.active_card.country,
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'
account.save()
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
charge = sc.create_charge(preapproval_amount, customer=customer, description="${0} for test / retain cc".format(preapproval_amount))
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)
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")
customer = None
charge = sc.create_charge(preapproval_amount, card=stripe_token, description="${0} for test / cc not retained".format(preapproval_amount))
# set True for now -- wondering whether we should actually wait for a webhook -- don't think so.
## settings to apply to transaction for TRANSACTION_STATUS_COMPLETE
#self.transaction.type = PAYMENT_TYPE_INSTANT
#self.transaction.approved = True
#self.transaction.status = TRANSACTION_STATUS_COMPLETE
#self.transaction.pay_key = charge.id
# settings to apply to transaction for TRANSACTION_STATUS_ACTIVE
# should approved be set to False and wait for a webhook?
self.transaction.type = PAYMENT_TYPE_AUTHORIZATION
self.transaction.approved = True
self.transaction.status = TRANSACTION_STATUS_ACTIVE
self.transaction.preapproval_key = charge.id
self.transaction.currency = 'USD'
self.transaction.amount = preapproval_amount
self.transaction.date_payment = now()
self.transaction.save()
return HttpResponse("charge id: {0} / customer: {1}".format(charge.id, customer))
class NonprofitCampaign(FormView):
template_name="nonprofit.html"
form_class = CCForm
def get_context_data(self, **kwargs):
context = super(NonprofitCampaign, self).get_context_data(**kwargs)
context['nonprofit'] = settings.NONPROFIT
context['get'] = self.request.GET
return context
def get_form_kwargs(self):
if self.request.method == 'POST':
return {'data':self.request.POST}
else:
return {'initial':self.request.GET }
def form_valid(self, form):
username=form.cleaned_data['username']
forward={'username':username}
forward['work_id']= form.cleaned_data['work_id']
amount=form.cleaned_data['preapproval_amount']
forward['cents']=int(100*(amount-int(amount)))
forward['amount']= int(amount)
forward['sent']= Sent.objects.create(user=username,amount=form.cleaned_data['preapproval_amount']).pk
token=signing.dumps(forward)
return HttpResponseRedirect(settings.BASE_URL + reverse('donation_credit',kwargs={'token':token}))
class DonationCredit(TemplateView):
template_name="donation_credit.html"
def get_context_data(self, **kwargs):
context = super(DonationCredit, self).get_context_data(**kwargs)
context['faqmenu']="donation"
context['nonprofit'] = settings.NONPROFIT
try:
envelope=signing.loads(kwargs['token'])
context['envelope']=envelope
except signing.BadSignature:
self.template_name="donation_error.html"
return context
try:
work = models.Work.objects.get(id=envelope['work_id'])
campaign=work.last_campaign()
except models.Work.DoesNotExist:
campaign = None
context['work']=work
try:
user = User.objects.get(username=envelope['username'])
except User.DoesNotExist:
self.template_name="donation_user_error.html"
context['error']='user does not exist'
return context
if user != self.request.user:
self.template_name="donation_user_error.html"
context['error']='wrong user logged in'
return context
try:
# check token not used
CreditLog.objects.get(sent=envelope['sent'])
context['error']='credit already registered'
return context
except CreditLog.DoesNotExist:
#not used yet!
amount=envelope['amount']+envelope['cents']/D(100)
CreditLog.objects.create(user=user,amount=amount,action='deposit',sent=envelope['sent'])
ts=Transaction.objects.filter(user=user,campaign=campaign,status=TRANSACTION_STATUS_NONE).order_by('-pk')
if ts.count()==0:
ts=Transaction.objects.filter(user=user,campaign=campaign,status=TRANSACTION_STATUS_MODIFIED).order_by('-pk')
if ts.count()>0:
t=ts[0]
credit_transaction(t,user, amount)
for t in ts[1:]:
t.status=TRANSACTION_STATUS_CANCELED
t.save()
context['transaction']=t
return context
else:
user.credit.add_to_balance(amount)
return context
class PledgeRechargeView(TemplateView):
@ -852,7 +921,7 @@ class PledgeRechargeView(TemplateView):
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()
user = self.request.user
@ -871,15 +940,10 @@ class PledgeRechargeView(TemplateView):
if transaction is not None:
# 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 )
paymentReason = "Unglue.it Recharge for {0}".format(campaign.name)
p = PaymentManager(embedded=False)
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, transaction.amount, expiry=expiry, campaign=campaign, list=None, user=user,
return_url=return_url, nevermind_url=nevermind_url, anonymous=transaction.anonymous, premium=transaction.premium,
paymentReason=paymentReason)
p = PaymentManager()
t, url = p.authorize(transaction, return_url=return_url, paymentReason=paymentReason)
logger.info("Recharge url: {0}".format(url))
else:
url = None
@ -900,11 +964,6 @@ class PledgeCompleteView(TemplateView):
after pledging, supporter receives email including thanks, work pledged, amount, expiry date, any next steps they should expect; others?
study other confirmation emails for their contents
after pledging, supporters are returned to a thank-you screen
should have prominent "thank you" or "congratulations" message
should have prominent share options
should suggest other works for supporters to explore (on what basis?)
link to work page? or to page on which supporter entered the process? (if the latter, how does that work with widgets?)
should note that a confirmation email has been sent to $email from $sender
should briefly note next steps (e.g. if this campaign succeeds you will be emailed on date X)
@ -916,10 +975,6 @@ class PledgeCompleteView(TemplateView):
# pick up all get and post parameters and display
context = super(PledgeCompleteView, self).get_context_data()
output = "pledge complete"
output += self.request.method + "\n" + str(self.request.REQUEST.items())
context["output"] = output
if self.request.user.is_authenticated():
user = self.request.user
else:
@ -938,12 +993,8 @@ class PledgeCompleteView(TemplateView):
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
else:
if user.id != transaction.user.id:
# should be 403 -- but let's try 404 for now -- 403 exception coming in Django 1.4
raise Http404
except Exception, e:
@ -961,7 +1012,7 @@ class PledgeCompleteView(TemplateView):
correct_transaction_type = False
# add the work corresponding to the Transaction on the user's wishlist if it's not already on the wishlist
if user is not None and correct_user and correct_transaction_type and (campaign is not None) and (work is not None):
if user is not None and correct_transaction_type and (campaign is not None) and (work is not None):
# ok to overwrite Wishes.source?
user.wishlist.add_work(work, 'pledging')
@ -970,8 +1021,6 @@ class PledgeCompleteView(TemplateView):
works2 = worklist[4:8]
context["transaction"] = transaction
context["correct_user"] = correct_user
context["correct_transaction_type"] = correct_transaction_type
context["work"] = work
context["campaign"] = campaign
context["faqmenu"] = "complete"
@ -981,6 +1030,11 @@ class PledgeCompleteView(TemplateView):
return context
class PledgeModifiedView(PledgeCompleteView):
def get_context_data(self):
context = super(PledgeModifiedView, self).get_context_data()
context['modified']=True
return context
class PledgeCancelView(FormView):
"""A view for allowing a user to cancel the active transaction for specified campaign"""
@ -1076,113 +1130,6 @@ class PledgeCancelView(FormView):
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.")
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):
template_name="donate.html"
form_class = DonateForm
embedded = False
#def get_context_data(self, **kwargs):
# context = super(DonateView, self).get_context_data(**kwargs)
#
# form = CampaignPledgeForm(data)
#
# context.update({'work':work,'campaign':campaign, 'premiums':premiums, 'form':form, 'premium_id':premium_id})
# return context
def form_valid(self, form):
donation_amount = form.cleaned_data["donation_amount"]
anonymous = form.cleaned_data["anonymous"]
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
campaign = None
p = PaymentManager(embedded=self.embedded)
# 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():
user = self.request.user
else:
user = None
# instant payment: send to the partnering RH
receiver_list = [{'email':settings.PAYPAL_NONPROFIT_PARTNER_EMAIL, 'amount':donation_amount}]
#redirect the page back to campaign page on success
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,
return_url=return_url, anonymous=anonymous)
if url:
return HttpResponseRedirect(url)
else:
response = t.reference
logger.info("PledgeView paypal: Error " + str(t.reference))
return HttpResponse(response)
def claim(request):
if request.method == 'GET':
data = request.GET
@ -2102,3 +2049,4 @@ def download(request, work_id):
def about(request, facet):
template = "about_" + facet + ".html"
return render(request, template)

View File

@ -1,870 +0,0 @@
from regluit.payment.parameters import *
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.models import Transaction, PaymentResponse
from boto.fps.connection import FPSConnection
from django.http import HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden
from datetime import timedelta
from regluit.utils.localdatetime import now, zuluformat
from boto import handler
from boto.resultset import ResultSet
from boto.exception import FPSResponseError
import xml.sax
import traceback
import datetime
import logging
import urlparse
import time
import urllib
logger = logging.getLogger(__name__)
AMAZON_STATUS_SUCCESS_ABT = 'SA'
AMAZON_STATUS_SUCCESS_ACH = 'SB'
AMAZON_STATUS_SUCCESS_CREDIT = 'SC'
AMAZON_STATUS_ERROR = 'SE'
AMAZON_STATUS_ADBANDONED = 'A'
AMAZON_STATUS_EXCEPTION = 'CE'
AMAZON_STATUS_PAYMENT_MISMATCH = 'PE'
AMAZON_STATUS_INCOMPLETE = 'NP'
AMAZON_STATUS_NOT_REGISTERED = 'NM'
AMAZON_STATUS_CANCELED = 'Canceled'
AMAZON_STATUS_FAILURE = 'Failure'
AMAZON_STATUS_PENDING = 'Pending'
AMAZON_STATUS_RESERVED = 'Reserved'
AMAZON_STATUS_SUCCESS = 'Success'
AMAZON_IPN_STATUS_CANCELED = 'CANCELED'
AMAZON_IPN_STATUS_FAILURE = 'FAILURE'
AMAZON_IPN_STATUS_PENDING = 'PENDING'
AMAZON_IPN_STATUS_RESERVED = 'RESERVED'
AMAZON_IPN_STATUS_SUCCESS = 'SUCCESS'
AMAZON_NOTIFICATION_TYPE_STATUS = 'TransactionStatus'
AMAZON_NOTIFICATION_TYPE_CANCEL = 'TokenCancellation'
AMAZON_OPERATION_TYPE_PAY = 'PAY'
AMAZON_OPERATION_TYPE_REFUND = 'REFUND'
AMAZON_OPERATION_TYPE_CANCEL = 'CANCEL'
# load FPS_ACCESS_KEY and FPS_SECRET_KEY from the database if possible
try:
from regluit.core.models import Key
FPS_ACCESS_KEY = Key.objects.get(name="FPS_ACCESS_KEY").value
FPS_SECRET_KEY = Key.objects.get(name="FPS_SECRET_KEY").value
logger.info('Successful loading of FPS_*_KEYs')
except Exception, e:
logger.info('EXCEPTION: unsuccessful loading of FPS_*_KEYs: {0}'.format(e))
def get_ipn_url():
if settings.IPN_SECURE_URL:
return settings.BASE_URL_SECURE + reverse('HandleIPN', args=["amazon"])
else:
return settings.BASE_URL + reverse('HandleIPN', args=["amazon"])
def ProcessIPN(request):
'''
IPN handler for amazon. Here is a litle background on amazon IPNS
http://docs.amazonwebservices.com/AmazonFPS/latest/FPSAdvancedGuide/APPNDX_IPN.html
notificationType: Can either be TransactionStatus of TokenCancellation
status: One of the defined IPN status codes
operation: The type of operation
callerReference: The reference to find the transaction
The IPN is called for the following cases:
A payment or reserve succeeds
A payment or reserve fails
A payment or reserve goes into a pending state
A reserved payment is settled successfully
A reserved payment is not settled successfully
A refund succeeds
A refund fails
A refund goes into a pending state
A payment is canceled
A reserve is canceled
A token is canceled successfully
'''
try:
logging.debug("Amazon IPN called")
logging.debug(request.POST)
uri = request.build_absolute_uri()
parsed_url = urlparse.urlparse(uri)
connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
# Check the validity of the IPN
resp = connection.verify_signature("%s://%s%s" %(parsed_url.scheme,
parsed_url.netloc,
parsed_url.path),
request.raw_post_data)
if not resp[0].VerificationStatus == "Success":
# Error, ignore this IPN
logging.error("Amazon IPN cannot be verified with post data: ")
logging.error(request.raw_post_data)
return HttpResponseForbidden()
logging.debug("Amazon IPN post data:")
logging.debug(request.POST)
reference = request.POST['callerReference']
type = request.POST['notificationType']
# In the case of cancelling a token, there is no transaction, so this info is not set
transactionId = request.POST.get('transactionId', None)
date = request.POST.get('transactionDate', None)
operation = request.POST.get('operation', None)
status = request.POST.get('transactionStatus', None)
logging.info("Received Amazon IPN with the following data:")
logging.info("type = %s" % type)
logging.info("operation = %s" % operation)
logging.info("reference = %s" % reference)
logging.info("status = %s" % status)
# We should always find the transaction by the token
transaction = Transaction.objects.get(secret=reference)
if type == AMAZON_NOTIFICATION_TYPE_STATUS:
# status update for the token, save the actual value
transaction.local_status = status
# Now map our local status to the global status codes
if operation == AMAZON_OPERATION_TYPE_PAY:
if status == AMAZON_IPN_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_COMPLETE
elif status == AMAZON_IPN_STATUS_PENDING:
if transaction.status == TRANSACTION_STATUS_CREATED:
#
# Per the amazon documentation:
# If your IPN receiving service is down for some time, it is possible that our retry mechanism will deliver the IPNs out of order.
# If you receive an IPN for TransactionStatus (IPN), as SUCCESS or FAILURE or RESERVED,
# then after that time ignore any IPN that gives the PENDING status for the transaction
#
transaction.status = TRANSACTION_STATUS_PENDING
else:
transaction.status = TRANSACTION_STATUS_ERROR
elif operation == AMAZON_OPERATION_TYPE_REFUND:
if status == AMAZON_IPN_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_REFUNDED
elif status == AMAZON_IPN_STATUS_PENDING:
transaction.status = TRANSACTION_STATUS_PENDING
else:
transaction.status = TRANSACTION_STATUS_ERROR
elif operation == AMAZON_OPERATION_TYPE_CANCEL:
if status == AMAZON_IPN_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_COMPLETE
else:
transaction.status = TRANSACTION_STATUS_ERROR
elif type == AMAZON_NOTIFICATION_TYPE_CANCEL:
#
# The cancel IPN does not have a transaction ID or transaction status, so make them up
#
transaction.local_status = AMAZON_IPN_STATUS_CANCELED
transaction.status = TRANSACTION_STATUS_CANCELED
status = AMAZON_IPN_STATUS_CANCELED
transaction.save()
#
# This is currently not done in paypal land, but log this IPN since the amazon IPN has good info
#
PaymentResponse.objects.create(api="IPN",
correlation_id = transactionId,
timestamp = date,
info = str(request.POST),
status=status,
transaction=transaction)
return HttpResponse("Complete")
except:
traceback.print_exc()
return HttpResponseForbidden()
def amazonPaymentReturn(request):
'''
This is the complete view called after the co-branded API completes. It is called whenever the user
approves a preapproval or a pledge. This URL is set via the PAY api.
'''
try:
transaction = None
# pick up all get and post parameters and display
output = "payment complete"
output += request.method + "\n" + str(request.REQUEST.items())
signature = request.GET['signature']
status = request.GET['status']
reference = request.GET['callerReference']
token = request.GET['tokenID']
# validate the signature
uri = request.build_absolute_uri()
parsed_url = urlparse.urlparse(uri)
connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
# Check the validity of the IPN
resp = connection.verify_signature("%s://%s%s" %(parsed_url.scheme,
parsed_url.netloc,
parsed_url.path),
urllib.urlencode(request.GET))
if not resp[0].VerificationStatus == "Success":
# Error, ignore this
logging.error("amazonPaymentReturn cannot be verified with get data: ")
logging.error(request.GET)
return HttpResponseForbidden()
logging.debug("amazonPaymentReturn sig verified:")
logging.debug(request.GET)
# validation of signature ok
# Find the transaction by reference, there should only be one
try:
transaction = Transaction.objects.get(secret=reference)
except:
logging.info("transaction with secret {0}".format(reference))
return HttpResponseForbidden()
logging.info("Amazon Co-branded Return URL called for transaction id: %d" % transaction.id)
logging.info(request.GET)
#
# BUGBUG, for now lets map amazon status code to paypal, just to keep things uninform
#
if transaction.type == PAYMENT_TYPE_INSTANT:
# Instant payments need to be executed now
# Log the authorize transaction
r = PaymentResponse.objects.create(api="Authorize",
correlation_id = "None",
timestamp = str(datetime.datetime.now()),
info = str(request.GET),
status=status,
transaction=transaction)
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
# The above status code are unique to the return URL and are different than the pay API codes
# Store the token, we need this for the IPN.
transaction.pay_key = token
#
# BUGBUG, need to handle multiple recipients
# Send the pay request now to ourselves
#
e = Execute(transaction=transaction)
if e.success() and not e.error():
# Success case, save the ID. Our IPN will update the status
print "Amazon Execute returned succesfully"
else:
logging.error("Amazon payment execution failed: ")
logging.error(e.envelope())
transaction.status = TRANSACTION_STATUS_ERROR
# Log the pay transaction
r = PaymentResponse.objects.create(api="Pay",
correlation_id = e.correlation_id(),
timestamp = e.timestamp(),
info = e.envelope(),
status = e.status,
transaction=transaction)
else:
# We may never see an IPN, set the status here
logging.error("Amazon payment authorization failed: ")
logging.error(request.GET)
transaction.status = TRANSACTION_STATUS_ERROR
elif transaction.type == PAYMENT_TYPE_AUTHORIZATION:
#
# Future payments, we only need to store the token. The authorization was requested with the default expiration
# date set in our settings. When we are ready, we can call execute on this
#
transaction.local_status = status
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
# The above status code are unique to the return URL and are different than the pay API codes
transaction.status = TRANSACTION_STATUS_ACTIVE
transaction.approved = True
transaction.pay_key = token
transaction.save()
print "Calling CANCEL RELATED"
# clear out any other active transactions for this user and this campaign
from regluit.payment.manager import PaymentManager
p = PaymentManager()
p.cancel_related_transaction(transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=transaction.campaign)
else:
# We may never see an IPN, set the status here
transaction.status = TRANSACTION_STATUS_ERROR
# Log the trasaction
r = PaymentResponse.objects.create(api="Authorize",
correlation_id = "None",
timestamp = str(datetime.datetime.now()),
info = str(request.GET),
status = status,
transaction=transaction)
else:
# Corrupt transaciton, unknown type
transaction.status = TRANSACTION_STATUS_ERROR
transaction.save()
if transaction.status == TRANSACTION_STATUS_ERROR:
# We failed, redirect to a page to allow the user to try again
return_path = "{0}?{1}".format(reverse('pledge_nevermind'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
else:
# Not a failure, exact status will be updated by IPN
# Redirect to our pledge success URL
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
except Exception, e:
logging.error("Amazon co-branded return-url FAILED with exception:")
traceback.print_exc()
if transaction:
# We failed, redirect to a page to allow the user to try again
return_path = "{0}?{1}".format(reverse('pledge_nevermind'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
else:
#
# If we are here, amazon did not give us a caller reference, so we don't know what transaction was cancelled.
# Some kind of cleanup is required. This can happen if there is an error in the amazon API or if the user clicks
# the cancel button. If the case where the user closes the co-branded window or hits the back button, we will never arrive here.
#
return HttpResponseRedirect(settings.BASE_URL)
class AmazonRequest:
'''
Handles common information that is processed from the response envelope of the amazon request.
'''
# Global values for the class
response = None
raw_response = None
errorMessage = None
status = None
url = None
def ack( self ):
return None
def success(self):
if self.errorMessage:
return False
else:
return True
def error(self):
if self.errorMessage:
return True
else:
return False
def error_data(self):
return None
def error_id(self):
return None
def error_string(self):
return self.errorMessage
def envelope(self):
# The envelope is used to store info about this request
if self.response:
return str(self.response)
else:
return None
def correlation_id(self):
# The correlation ID is unique to each API call
if self.response:
return self.response.TransactionId
else:
return None
def timestamp(self):
return str(datetime.datetime.now())
class Pay( AmazonRequest ):
'''
The pay function generates a redirect URL to approve the transaction
'''
def __init__( self, transaction, return_url=None, nevermind_url=None, amount=None, paymentReason=""):
try:
logging.debug("Amazon PAY operation for transaction ID %d" % transaction.id)
# Replace our return URL with a redirect through our internal URL
self.original_return_url = return_url
return_url = settings.BASE_URL + reverse('AmazonPaymentReturn')
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
receiver_list = []
receivers = transaction.receiver_set.all()
if not amount:
amount = 0
for r in receivers:
amount += r.amount
logger.info(receiver_list)
# Data fields for amazon
expiry = now() + timedelta( days=settings.PREAPPROVAL_PERIOD )
data = {
'amountType':'Maximum', # The transaction amount is the maximum amount
'callerReference': transaction.secret,
'currencyCode': 'USD',
'globalAmountLimit': str(amount),
'validityExpiry': str(int(time.mktime(expiry.timetuple()))), # use the preapproval date by default
}
self.url = self.connection.make_url(return_url, paymentReason, "MultiUse", str(amount), **data)
logging.debug("Amazon PAY redirect url was: %s" % self.url)
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon PAY api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon PAY FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
def api(self):
return "Amazon Co-branded PAY request"
def exec_status( self ):
return None
def amount( self ):
return None
def key( self ):
return None
def next_url( self ):
return self.url
def embedded_url(self):
return None
class Preapproval(Pay):
def __init__( self, transaction, amount, expiry=None, return_url=None, nevermind_url=None, paymentReason=""):
# set the expiration date for the preapproval if not passed in. This is what the paypal library does
now_val = now()
if expiry is None:
expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
transaction.date_authorized = now_val
transaction.date_expired = expiry
transaction.save()
# Call into our parent class
Pay.__init__(self, transaction, return_url=return_url, nevermind_url=nevermind_url, amount=amount, paymentReason=paymentReason)
class Execute(AmazonRequest):
'''
The Execute function sends an existing token(generated via the URL from the pay operation), and collects
the money.
'''
def __init__(self, transaction=None):
try:
logging.debug("Amazon EXECUTE action for transaction id: %d" % transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
# BUGBUG, handle multiple receivers! For now we just send the money to ourselves
global_params = {"OverrideIPNURL": get_ipn_url()}
self.raw_response = self.connection.pay(transaction.amount,
transaction.pay_key,
recipientTokenId=None,
callerReference=transaction.secret,
senderReference=None,
recipientReference=None,
senderDescription=None,
recipientDescription=None,
callerDescription=None,
metadata=None,
transactionDate=None,
reserve=False,
extra_params=global_params)
#
# BUGBUG:
# The boto FPS library throws an exception if an error is generated, we need to do a better
# job of reporting the error when this occurs
#
self.response = self.raw_response[0]
logging.debug("Amazon EXECUTE response for transaction id: %d" % transaction.id)
logging.debug(str(self.response))
self.status = self.response.TransactionStatus
#
# For amazon, the transactionID is per transaction, not per receiver. For now we will store it in the preapproval key field
# so we can use it to refund or get status later
#
transaction.preapproval_key = self.response.TransactionId
logging.debug("Amazon EXECUTE API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon EXECUTE api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon EXECUTE FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
def api(self):
return "Amazon API Pay"
def key(self):
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key
return self.transaction.pay_key
class Finish(AmazonRequest):
'''
The Finish function handles the secondary receiver in a chained payment. Currently not implemented
for amazon
'''
def __init__(self, transaction):
try:
print "Finish"
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class PaymentDetails(AmazonRequest):
'''
Get details about executed PAY operation
This api must set the following class variables to work with the code in manager.py
status - one of the global transaction status codes
transactions -- Not supported for amazon, used by paypal
'''
def __init__(self, transaction=None):
try:
logging.debug("Amazon PAYMENTDETAILS API for transaction id: %d" % transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
if not transaction.preapproval_key:
# This is where we store the transaction ID
self.errorMessage = "No Valid Transaction ID"
return
#
# We need to reference the transaction ID here, this is stored in the preapproval_key as this
# field is not used for amazon
#
self.raw_response = self.connection.get_transaction_status(transaction.preapproval_key)
self.response = self.raw_response[0]
logging.debug("Amazon PAYMENTDETAILS API for transaction id: %d returned response:")
logging.debug(self.response)
#
# Now we need to build values to match the paypal response.
# The two we need are status and and array of transactions.
#
# Check our status codes, note that these are different than the IPN status codes
self.local_status = self.response.StatusCode
self.message = self.response.StatusMessage
if self.local_status == 'Canceled':
self.status = TRANSACTION_STATUS_CANCELED
elif self.local_status == 'Success':
#
# Note, there is a limitation here. If the current status is refunded, this API will return "Success".
# We must be careful to not overwrite refunded status codes. There is no way that I can find to poll
# to see if a transaction is refunded. I need to investigate all of the data fields and see if we can find
# that information
#
if transaction.status != TRANSACTION_STATUS_REFUNDED:
self.status = TRANSACTION_STATUS_COMPLETE
else:
self.status = TRANSACTION_STATUS_REFUNDED
elif self.local_status == 'PendingNetworkResponse' or self.local_status == 'PendingVerification':
self.status = TRANSACTION_STATUS_PENDING
elif self.local_status == 'TransactionDenied':
self.status = TRANSACTION_STATUS_FAILED
else:
self.status = TRANSACTION_STATUS_ERROR
# Amazon does not support receivers at this point
self.transactions = []
logging.debug("Amazon PAYMENTDETAILS API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon PAYMENTDETAILS api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon PAYMENTDETAILS FAILED with exception:")
self.errorMessage = "Error: ServerError"
traceback.print_exc()
class CancelPreapproval(AmazonRequest):
'''
Cancels an exisiting token. The current boto FPS library does not directly support
the CancelToken API, just the Cancel API(for real money in-flight or reserved).
'''
def __init__(self, transaction):
try:
logging.debug("Amazon CANCELPREAPPROVAL api called for transaction id: %d" % transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
global_params = {"OverrideIPNURL": get_ipn_url()}
params = global_params
params['TokenId'] = transaction.pay_key
params['ReasonText'] = "Cancel Reason"
fps_response = self.connection.make_request("CancelToken", params)
body = fps_response.read()
if(fps_response.status == 200):
rs = ResultSet()
h = handler.XmlHandler(rs, self)
xml.sax.parseString(body, h)
if rs:
self.raw_response = rs
self.response = self.raw_response[0]
self.status = self.response.TransactionStatus
self.errorMessage = None
else:
#
# Set an error message and failure status for
# our success() and error() functions
#
self.status = AMAZON_STATUS_FAILURE
self.errorMessage = "%s - %s" % (fps_response.reason, body)
logging.debug("Amazon CANCELPREAPPROVAL API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon CANCELPREAPPROVAL api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon CANCELPREAPPROVAL FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class RefundPayment(AmazonRequest):
def __init__(self, transaction):
try:
logging.debug("Amazon REFUNDPAYMENT API called for transaction id: %d", transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
if not transaction.preapproval_key:
# This is where we store the transaction ID
self.errorMessage = "No Valid Transaction ID"
return
#
# We need to reference the transaction ID here, this is stored in the preapproval_key as this
# field is not used for amazon
#
global_params = {"OverrideIPNURL": get_ipn_url()}
self.raw_response = self.connection.refund(transaction.secret, transaction.preapproval_key, extra_params=global_params)
self.response = self.raw_response[0]
logging.debug("Amazon REFUNDPAYMENT response was:")
logging.debug(str(self.response))
self.status = self.response.TransactionStatus
logging.debug("Amazon REFUNDPAYMENT API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon REFUNDPAYMENT api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon REFUNDPAYMENT FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class PreapprovalDetails(AmazonRequest):
'''
Get details about an authorized token
This api must set 4 different class variables to work with the code in manager.py
status - one of the global transaction status codes
approved - boolean value
currency - not used in this API, but we can get some more info via other APIs - TODO
amount - not used in this API, but we can get some more info via other APIs - TODO
'''
def __init__(self, transaction=None):
try:
logging.debug("Amazon PREAPPROVALDETAILS API called for transaction id: %d", transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
#
# We need to reference the caller reference here, we may not have a token if the return URL failed
#
self.raw_response = self.connection.get_token_by_caller_reference(transaction.secret)
self.response = self.raw_response
logging.debug("Amazon PREAPPROVALDETAILS response:")
logging.debug(str(self.response))
#
# Look for a token, we store this in the pay_key field
#
self.pay_key = self.response.TokenId
self.local_status = self.response.TokenStatus
# Possible status for the Token object are Active and Inactive
if self.local_status == 'Active':
self.status = TRANSACTION_STATUS_ACTIVE
self.approved = True
else:
# It is not clear here if this should be failed or cancelled, but we have no way to know
# the token is only active or now, so we will assume it is canceled.
self.status = TRANSACTION_STATUS_CANCELED
self.approved = False
# Set the other fields that are expected. We don't have values for these now, so just copy the transaction
self.currency = transaction.currency
self.amount = transaction.amount
logging.debug("Amazon PREAPPROVALDETAILS API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon PREAPPROVALDETAILS api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
# If the boto API fails, it also throws an exception and we end up here
logging.error("Amazon PREAPPROVALDETAILS FAILED with exception:")
self.errorMessage = "Error: ServerError"
traceback.print_exc()

182
payment/baseprocessor.py Normal file
View File

@ -0,0 +1,182 @@
from regluit.payment.models import Transaction, PaymentResponse
from django.http import HttpResponseForbidden
from datetime import timedelta
from regluit.utils.localdatetime import now, zuluformat
import datetime
import time
def ProcessIPN(request):
return HttpResponseForbidden()
class BasePaymentRequest:
'''
Handles common information incident to payment processing
'''
# Global values for the class
response = None
raw_response = None
errorMessage = None
status = None
url = None
def ack( self ):
return None
def success(self):
if self.errorMessage:
return False
else:
return True
def error(self):
if self.errorMessage:
return True
else:
return False
def error_data(self):
return None
def error_id(self):
return None
def error_string(self):
return self.errorMessage
def envelope(self):
# The envelope is used to store info about this request
if self.response:
return str(self.response)
else:
return None
def correlation_id(self):
return None
def timestamp(self):
return str(datetime.datetime.now())
class Pay( BasePaymentRequest ):
'''
The pay function generates a redirect URL to approve the transaction
'''
def __init__( self, transaction, return_url=None, amount=None, paymentReason=""):
self.transaction=transaction
def api(self):
return "null api"
def exec_status( self ):
return None
def amount( self ):
return None
def key( self ):
return None
def next_url( self ):
return self.url
class Preapproval(Pay):
def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""):
# set the expiration date for the preapproval if not passed in. This is what the paypal library does
now_val = now()
if expiry is None:
expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
transaction.date_authorized = now_val
transaction.date_expired = expiry
transaction.save()
# Call into our parent class
Pay.__init__(self, transaction, return_url=return_url, amount=amount, paymentReason=paymentReason)
class Execute(BasePaymentRequest):
'''
The Execute function sends an existing token(generated via the URL from the pay operation), and collects
the money.
'''
def __init__(self, transaction=None):
self.transaction = transaction
def api(self):
return "Base Pay"
def key(self):
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key
return self.transaction.pay_key
class Finish(BasePaymentRequest):
'''
The Finish function handles the secondary receiver in a chained payment.
'''
def __init__(self, transaction):
print "Finish"
class PaymentDetails(BasePaymentRequest):
'''
Get details about executed PAY operation
This api must set the following class variables to work with the code in manager.py
status - one of the global transaction status codes
transactions -- Not supported for amazon, used by paypal
'''
def __init__(self, transaction=None):
self.transaction = transaction
class CancelPreapproval(BasePaymentRequest):
'''
Cancels an exisiting token.
'''
def __init__(self, transaction):
self.transaction = transaction
class RefundPayment(BasePaymentRequest):
def __init__(self, transaction):
self.transaction = transaction
class PreapprovalDetails(BasePaymentRequest):
'''
Get details about an authorized token
This api must set 4 different class variables to work with the code in manager.py
status - one of the global transaction status codes
approved - boolean value
currency - not used in this API, but we can get some more info via other APIs - TODO
amount - not used in this API, but we can get some more info via other APIs - TODO
'''
def __init__(self, transaction=None):
self.transaction = transaction

55
payment/credit.py Normal file
View File

@ -0,0 +1,55 @@
from datetime import timedelta
from django.contrib.auth.models import User
from django.conf import settings
from regluit.payment.parameters import *
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.max_amount=amount
t.set_credit_approved(amount)
def credit_transaction(t,user,amount):
'''user has new credit, use it to fund the transaction'''
# first, credit the user's account
user.credit.add_to_balance(amount)
# now pledge to the transaction
pledge_amount = t.max_amount if t.max_amount <= user.credit.available else amount
user.credit.add_to_pledged(pledge_amount)
t.set_credit_approved(pledge_amount)
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

7
payment/forms.py Normal file
View File

@ -0,0 +1,7 @@
from django import forms
import logging
logger = logging.getLogger(__name__)
class StripePledgeForm(forms.Form):
stripe_token = forms.CharField(required=False, widget=forms.HiddenInput())

View File

@ -0,0 +1,13 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
import regluit.payment
class Command(BaseCommand):
help = "initialize credit table"
def handle(self, *args, **kwargs):
users = User.objects.all()
for user in users:
regluit.payment.models.Credit(user=user).save()

View File

@ -0,0 +1,16 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
class Command(BaseCommand):
help = "grant credit to a user"
args = "<username> <amount> <action>"
def handle(self, username, amount, action="credit", *args, **kwargs):
if action=="debit":
amount=-int(amount)
else:
amount= int(amount)
user = User.objects.get(username=username)
user.credit.add_to_balance(amount)
print "%s now has a balance of %s donation credits" % (username, user.credit.balance)

View File

@ -4,15 +4,11 @@ from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.parameters import *
from regluit.payment.paypal import IPN_SENDER_STATUS_COMPLETED
from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created
from regluit.payment import credit
if settings.PAYMENT_PROCESSOR == 'paypal':
from regluit.payment.paypal import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
from regluit.payment.paypal import Pay as Execute
from regluit.payment.baseprocessor import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
elif settings.PAYMENT_PROCESSOR == 'amazon':
from regluit.payment.amazon import Pay, Execute, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
import uuid
import traceback
from regluit.utils.localdatetime import now
@ -21,6 +17,8 @@ import logging
from decimal import Decimal as D
from xml.dom import minidom
import urllib, urlparse
from datetime import timedelta
from django.conf import settings
@ -38,9 +36,6 @@ def append_element(doc, parent, name, text):
# at this point, there is no internal context and therefore, the methods of PaymentManager can be recast into static methods
class PaymentManager( object ):
def __init__( self, embedded=False):
self.embedded = embedded
def processIPN(self, request, module):
# Forward to our payment processor
@ -541,24 +536,13 @@ class PaymentManager( object ):
logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
return False
def authorize(self, currency, target, amount, expiry=None, campaign=None, list=None, user=None,
return_url=None, nevermind_url=None, anonymous=False, premium=None,
paymentReason="unglue.it Pledge", modification=False):
def authorize(self, transaction, expiry= None, return_url=None, paymentReason="unglue.it Pledge", modification=False):
'''
authorize
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
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
modification: whether this authorize call is part of a modification of an existing pledge
@ -567,27 +551,13 @@ class PaymentManager( object ):
'''
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
)
if host==None:
#TODO send user to select a payment processor
pass
# we might want to not allow for a return_url or nevermind_url to be passed in but calculated
# 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.
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:
return_path = "{0}?{1}".format(reverse('pledge_complete'),
@ -595,7 +565,7 @@ class PaymentManager( object ):
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
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
envelope = p.envelope()
@ -605,11 +575,11 @@ class PaymentManager( object ):
correlation_id = p.correlation_id(),
timestamp = p.timestamp(),
info = p.raw_response,
transaction=t)
transaction=transaction)
if p.success() and not p.error():
t.preapproval_key = p.key()
t.save()
transaction.preapproval_key = p.key()
transaction.save()
url = p.next_url()
@ -629,16 +599,62 @@ class PaymentManager( object ):
# 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
# authorize a charge
pledge_created.send(sender=self, transaction=t)
pledge_created.send(sender=self, transaction=transaction)
return t, url
return transaction, url
else:
t.error = p.error_string()
t.save()
transaction.error = p.error_string()
transaction.save()
logger.info("Authorize Error: " + p.error_string())
return t, None
return transaction, None
def process_transaction(self, currency, amount, host=None, campaign=None, user=None,
return_url=None, paymentReason="unglue.it Pledge", pledge_extra=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
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
pledge_extra: extra pledge stuff
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.create(amount=0,
max_amount=amount,
currency=currency,
campaign=campaign,
user=user,
pledge_extra=pledge_extra
)
t.save()
# 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:
# send user to choose payment path
return t, reverse('fund_pledge', args=[t.id])
def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None):
'''
@ -682,26 +698,26 @@ class PaymentManager( object ):
return canceled
def modify_transaction(self, transaction, amount, expiry=None, anonymous=None, premium=None,
return_url=None, nevermind_url=None,
paymentReason=None):
def modify_transaction(self, transaction, amount, expiry=None, pledge_extra=None,
return_url=None, nevermind_url=None, paymentReason=None):
'''
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
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
premium: new premium selected; if None, then keep old value
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
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:
logger.info("Error, attempt to modify an invalid transaction type")
return False, None
@ -712,31 +728,57 @@ class PaymentManager( object ):
logger.info("Error, attempt to modify a transaction that is not active")
return False, None
# if any of expiry, anonymous, or premium is None, use the existing value
if expiry is None:
expiry = transaction.date_expired
if anonymous is None:
anonymous = transaction.anonymous
if premium is None:
premium = transaction.premium
if transaction.host == PAYMENT_HOST_CREDIT:
# does user have enough credit to pledge now?
if transaction.user.credit.available >= amount-transaction.amount :
# YES!
transaction.set_pledge_extra(pledge_extra)
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)
if amount > transaction.max_amount or expiry != transaction.date_expired:
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:
# cancel old transaction, send user to choose new payment path
# set the expiry date based on the campaign deadline
expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
t = Transaction.create(amount=0,
max_amount=amount,
currency=transaction.currency,
status=TRANSACTION_STATUS_MODIFIED,
campaign=transaction.campaign,
user=transaction.user,
pledge_extra=pledge_extra
)
t.save()
credit.CancelPreapproval(transaction)
return t, reverse('fund_pledge', args=[t.id])
# Start a new authorization for the new amount
elif amount > transaction.max_amount or expiry != transaction.date_expired:
t, url = self.authorize(transaction.currency,
transaction.target,
amount,
expiry,
transaction.campaign,
transaction.list,
transaction.user,
return_url,
nevermind_url,
transaction.anonymous,
premium,
paymentReason,
True)
# 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.create(amount=amount,
max_amount=amount,
host=transaction.host,
currency=transaction.currency,
status=TRANSACTION_STATUS_CREATED,
campaign=transaction.campaign,
user=transaction.user,
pledge_extra=pledge_extra
)
t.save()
t, url = self.authorize(transaction,
expiry=expiry if expiry else transaction.date_expired,
return_url=return_url,
paymentReason=paymentReason,
modification=True
)
if t and url:
# Need to re-direct to approve the transaction
@ -761,8 +803,7 @@ class PaymentManager( object ):
elif amount <= transaction.max_amount:
# Update transaction but leave the preapproval alone
transaction.amount = amount
transaction.anonymous = anonymous
transaction.premium = premium
transaction.set_pledge_extra(pledge_extra)
transaction.save()
logger.info("Updated amount of transaction to %f" % amount)
@ -819,28 +860,18 @@ class PaymentManager( object ):
logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
return False
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None, return_url=None, nevermind_url=None, anonymous=False, premium=None):
def pledge(self, currency, campaign=None, user=None,
return_url=None, nevermind_url=None, pledge_extra=None):
'''
pledge
Performs an instant payment
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:
[
{'email':'email-1', 'amount':amount1},
{'email':'email-2', 'amount':amount2}
]
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
list: optional list object(to be set with TARGET_TYPE_LIST)
campaign: required campaign object
user: optional user object
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
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
the redirect url will be None
@ -849,25 +880,18 @@ class PaymentManager( object ):
amount = D('0.00')
# for chained payments, first amount is the total amount
amount = D(receiver_list[0]['amount'])
t = Transaction.objects.create(amount=amount,
t = Transaction.create(amount=amount,
max_amount=amount,
type=PAYMENT_TYPE_INSTANT,
execution=EXECUTE_TYPE_CHAINED_INSTANT,
target=target,
currency=currency,
status='NONE',
status=TRANSACTION_STATUS_NONE,
campaign=campaign,
list=list,
user=user,
date_payment=now(),
anonymous=anonymous,
premium=premium
pledge_extra=pledge_extra
)
t.create_receivers(receiver_list)
t.date_payment=now()
t.execution=EXECUTE_TYPE_CHAINED_INSTANT
t.type=PAYMENT_TYPE_INSTANT
method = getattr(t.get_payment_class(), "Pay")
p = method(t,return_url=return_url, nevermind_url=nevermind_url)
@ -887,10 +911,6 @@ class PaymentManager( object ):
t.status = TRANSACTION_STATUS_CREATED
t.save()
if self.embedded:
url = p.embedded_url()
logger.info(url)
else:
url = p.next_url()
logger.info("Pledge Success: " + url)

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

@ -0,0 +1,200 @@
# 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 'Sent'
db.create_table('payment_sent', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.CharField')(max_length=32, 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)),
))
db.send_create_signal('payment', ['Sent'])
# Adding field 'CreditLog.sent'
db.add_column('payment_creditlog', 'sent', self.gf('django.db.models.fields.IntegerField')(null=True), keep_default=False)
def backwards(self, orm):
# Deleting model 'Sent'
db.delete_table('payment_sent')
# Deleting field 'CreditLog.sent'
db.delete_column('payment_creditlog', 'sent')
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, 9, 5, 23, 7, 10, 823074)'}),
'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, 9, 5, 23, 7, 10, 822938)'}),
'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'}),
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.sent': {
'Meta': {'object_name': 'Sent'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Account'
db.create_table('payment_account', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('host', self.gf('django.db.models.fields.CharField')(default='none', max_length=32)),
('account_id', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
))
db.send_create_signal('payment', ['Account'])
def backwards(self, orm):
# Deleting model 'Account'
db.delete_table('payment_account')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'payment.account': {
'Meta': {'object_name': 'Account'},
'account_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'payment.credit': {
'Meta': {'object_name': 'Credit'},
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'payment.creditlog': {
'Meta': {'object_name': 'CreditLog'},
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.sent': {
'Meta': {'object_name': 'Sent'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -0,0 +1,270 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Account.card_last4'
db.add_column('payment_account', 'card_last4',
self.gf('django.db.models.fields.CharField')(max_length=4, null=True),
keep_default=False)
# Adding field 'Account.card_type'
db.add_column('payment_account', 'card_type',
self.gf('django.db.models.fields.CharField')(max_length=32, null=True),
keep_default=False)
# Adding field 'Account.card_exp_month'
db.add_column('payment_account', 'card_exp_month',
self.gf('django.db.models.fields.IntegerField')(null=True),
keep_default=False)
# Adding field 'Account.card_exp_year'
db.add_column('payment_account', 'card_exp_year',
self.gf('django.db.models.fields.IntegerField')(null=True),
keep_default=False)
# Adding field 'Account.card_fingerprint'
db.add_column('payment_account', 'card_fingerprint',
self.gf('django.db.models.fields.CharField')(max_length=32, null=True),
keep_default=False)
# Adding field 'Account.card_country'
db.add_column('payment_account', 'card_country',
self.gf('django.db.models.fields.CharField')(max_length=2, null=True),
keep_default=False)
# Adding field 'Account.date_created'
db.add_column('payment_account', 'date_created',
self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default=datetime.datetime(2012, 9, 17, 0, 0), blank=True),
keep_default=False)
# Adding field 'Account.date_modified'
db.add_column('payment_account', 'date_modified',
self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default=datetime.datetime(2012, 9, 17, 0, 0), blank=True),
keep_default=False)
# Adding field 'Account.date_deactivated'
db.add_column('payment_account', 'date_deactivated',
self.gf('django.db.models.fields.DateTimeField')(null=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Account.card_last4'
db.delete_column('payment_account', 'card_last4')
# Deleting field 'Account.card_type'
db.delete_column('payment_account', 'card_type')
# Deleting field 'Account.card_exp_month'
db.delete_column('payment_account', 'card_exp_month')
# Deleting field 'Account.card_exp_year'
db.delete_column('payment_account', 'card_exp_year')
# Deleting field 'Account.card_fingerprint'
db.delete_column('payment_account', 'card_fingerprint')
# Deleting field 'Account.card_country'
db.delete_column('payment_account', 'card_country')
# Deleting field 'Account.date_created'
db.delete_column('payment_account', 'date_created')
# Deleting field 'Account.date_modified'
db.delete_column('payment_account', 'date_modified')
# Deleting field 'Account.date_deactivated'
db.delete_column('payment_account', 'date_deactivated')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'payment.account': {
'Meta': {'object_name': 'Account'},
'account_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'card_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}),
'card_exp_month': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'card_exp_year': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'card_fingerprint': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'card_last4': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True'}),
'card_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_deactivated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'payment.credit': {
'Meta': {'object_name': 'Credit'},
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'payment.creditlog': {
'Meta': {'object_name': 'CreditLog'},
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.sent': {
'Meta': {'object_name': 'Sent'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -1,11 +1,23 @@
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from regluit.core.models import Campaign, Wishlist, Premium
from regluit.core.models import Campaign, Wishlist, Premium, PledgeExtra
from regluit.payment.parameters import *
from regluit.payment.signals import credit_balance_added, pledge_created
from regluit.utils.localdatetime import now
from decimal import Decimal
from datetime import timedelta
import uuid
import urllib
import logging
logger = logging.getLogger(__name__)
# in fitting stripe -- here are possible fields to fit in with Transaction
# c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid,
# c.fee, c.disputed, c.amount_refunded, c.failure_message,
# c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year
# promising fields
class Transaction(models.Model):
@ -13,16 +25,13 @@ class Transaction(models.Model):
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 = models.CharField(default=settings.PAYMENT_PROCESSOR, 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)
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
# status: general status constants defined in parameters.py
status = models.CharField(max_length=32, default='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 = models.CharField(max_length=32, default='NONE', null=True)
@ -72,12 +81,17 @@ class Transaction(models.Model):
campaign = models.ForeignKey(Campaign, null=True)
premium = models.ForeignKey(Premium, null=True)
# list: makes allowance for pledging against a Wishlist: not currently in use
list = models.ForeignKey(Wishlist, null=True)
# how to acknowledge the user on the supporter page of the campaign ebook
ack_name = models.CharField(max_length=64, null=True)
ack_dedication = models.CharField(max_length=140, null=True)
# whether the user wants to be not listed publicly
anonymous = models.BooleanField(null=False)
@property
def ack_link(self):
return 'https://unglue.it/supporter/%s'%urllib.urlencode(self.user.username) if not self.anonymous else ''
def save(self, *args, **kwargs):
if not self.secret:
self.secret = str(uuid.uuid1())
@ -103,6 +117,50 @@ class Transaction(models.Model):
mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)])
return mod
def set_credit_approved(self, amount):
self.amount=amount
self.host = PAYMENT_HOST_CREDIT
self.type = PAYMENT_TYPE_AUTHORIZATION
self.status=TRANSACTION_STATUS_ACTIVE
self.approved=True
now_val = now()
self.date_authorized = now_val
self.date_expired = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
self.save()
pledge_created.send(sender=self, transaction=self)
def set_pledge_extra(self, pledge_extra):
if pledge_extra:
self.anonymous = pledge_extra.anonymous
self.premium = pledge_extra.premium
self.ack_name = pledge_extra.ack_name
self.ack_dedication = pledge_extra.ack_dedication
def get_pledge_extra(self, pledge_extra):
return PledgeExtra(anonymous=self.anonymous,
premium=self.premium,
ack_name=self.ack_name,
ack_dedication=self.ack_dedication)
@classmethod
def create(cls,amount=0.00, host=PAYMENT_HOST_NONE, max_amount=0.00, currency='USD',
status=TRANSACTION_STATUS_NONE,campaign=None, user=None, pledge_extra=None):
if pledge_extra:
return cls.objects.create(amount=amount,
host=host,
max_amount=max_amount,
currency=currency,
status=status,
campaign=campaign,
user=user,
premium=pledge_extra.premium,
anonymous=pledge_extra.anonymous,
ack_name=pledge_extra.ack_name,
ack_dedication=pledge_extra.ack_dedication
)
else:
return cls.objects.create(amount=amount, host=host, max_amount=max_amount, currency=currency,status=status,
campaign=campaign, user=user)
class PaymentResponse(models.Model):
# The API used
@ -143,6 +201,120 @@ class Receiver(models.Model):
def __unicode__(self):
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)
# used to record the sent id when action = 'deposit'
sent=models.IntegerField(null=True)
class Credit(models.Model):
user = models.OneToOneField(User, related_name='credit')
balance = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
pledged = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
last_activity = models.DateTimeField(auto_now=True)
@property
def available(self):
return self.balance - self.pledged
def add_to_balance(self, num_credits):
if self.pledged - self.balance > num_credits : # negative to withdraw
return False
else:
self.balance = self.balance + num_credits
self.save()
try: # bad things can happen here if you don't return True
CreditLog(user = self.user, amount = num_credits, action="add_to_balance").save()
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
def add_to_pledged(self, num_credits):
num_credits=Decimal(num_credits)
if num_credits is Decimal('NaN'):
return False
if self.balance - self.pledged < num_credits :
return False
else:
self.pledged=self.pledged + num_credits
self.save()
try: # bad things can happen here if you don't return True
CreditLog(user = self.user, amount = num_credits, action="add_to_pledged").save()
except:
logger.exception("failed to log add_to_pledged of %s", num_credits)
return True
def use_pledge(self, num_credits):
if not isinstance( num_credits, int):
return False
if self.pledged < num_credits :
return False
else:
self.pledged=self.pledged - num_credits
self.balance = self.balance - num_credits
self.save()
try:
CreditLog(user = self.user, amount = - num_credits, action="use_pledge").save()
except:
logger.exception("failed to log use_pledge of %s", num_credits)
return True
def transfer_to(self, receiver, num_credits):
if not isinstance( num_credits, int) or not isinstance( receiver, User):
return False
if self.add_to_balance(-num_credits):
if receiver.credit.add_to_balance(num_credits):
return True
else:
# unwind transfer
self.add_to_balance(num_credits)
return False
else:
return False
class Sent(models.Model):
'''used by donation view to record donations it has sent'''
user = models.CharField(max_length=32, null=True)
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
timestamp = models.DateTimeField(auto_now=True)
class Account(models.Model):
"""holds references to accounts at third party payment gateways, especially for representing credit cards"""
# the following fields from stripe Customer might be relevant to Account -- we need to pick good selection
# c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc), c.account_balance, c.delinquent,
# c.active_card.fingerprint, c.active_card.type, c.active_card.last4, c.active_card.exp_month, c.active_card.exp_year,
# c.active_card.country
# host: the payment processor. Named after the payment module that hosts the payment processing functions
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
account_id = models.CharField(max_length=128, null=True)
# card related info
card_last4 = models.CharField(max_length=4, null=True)
# Visa, American Express, MasterCard, Discover, JCB, Diners Club, or Unknown
card_type = models.CharField(max_length=32, null=True)
card_exp_month = models.IntegerField(null=True)
card_exp_year = models.IntegerField(null=True)
card_fingerprint = models.CharField(max_length=32, null=True)
card_country = models.CharField(max_length=2, null=True)
# creation and last modified timestamps
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
date_deactivated = models.DateTimeField(null=True)
# associated User
user = models.ForeignKey(User, null=True)
from django.db.models.signals import post_save, post_delete
import regluit.payment.manager

View File

@ -5,29 +5,30 @@ PAYMENT_TYPE_AUTHORIZATION = 2
PAYMENT_HOST_NONE = "none"
PAYMENT_HOST_PAYPAL = "paypal"
PAYMENT_HOST_AMAZON = "amazon"
PAYMENT_HOST_STRIPE = "stripe"
PAYMENT_HOST_TEST = "test"
PAYMENT_HOST_CREDIT = "credit"
EXECUTE_TYPE_NONE = 0
EXECUTE_TYPE_CHAINED_INSTANT = 1
EXECUTE_TYPE_CHAINED_DELAYED = 2
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
TRANSACTION_STATUS_NONE = 'None'
# Indicates a transaction has been sent to the co-branded API
TRANSACTION_STATUS_CREATED = 'Created'
# A general complete code to indicate payment is comlete to all receivers
# A general complete code to indicate payment is complete to all receivers
TRANSACTION_STATUS_COMPLETE = 'Complete'
# A general pending code that means in process
TRANSACTION_STATUS_PENDING = 'Pending'
# This means that the max amount has increased but the increase hasn't been executed
TRANSACTION_STATUS_MODIFIED = 'Modified'
# Indicates a preapproval is active
TRANSACTION_STATUS_ACTIVE = 'Active'
@ -47,6 +48,3 @@ TRANSACTION_STATUS_REFUNDED = 'Refunded'
# The transaction was refused/denied
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'

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,38 @@
from notification import models as notification
from django.dispatch import Signal
transaction_charged = Signal(providing_args=["transaction"])
pledge_created = Signal(providing_args=["transaction"])
pledge_modified = Signal(providing_args=["transaction", "up_or_down"])
credit_balance_added = Signal(providing_args=["amount"])
from django.db.models.signals import post_save
from django.db.utils import DatabaseError
from django.db.models import get_model
from django.contrib.auth.models import User
# create Credit to associate with User
def create_user_objects(sender, created, instance, **kwargs):
# use get_model to avoid circular import problem with models
try:
Credit = get_model('payment', 'Credit')
if created:
Credit.objects.create(user=instance)
except DatabaseError:
# this can happen when creating superuser during syncdb since the
# core_wishlist table doesn't exist yet
return
post_save.connect(create_user_objects, sender=User)
def handle_credit_balance(sender, amount=0, **kwargs):
notification.queue([sender.user], "pledge_donation_credit", {
'user':sender.user,
'amount':amount,
'minus_amount':-amount
}, True)
from regluit.core.tasks import emit_notifications
emit_notifications.delay()
# successful_campaign -> send notices
credit_balance_added.connect(handle_credit_balance)

274
payment/stripelib.py Normal file
View File

@ -0,0 +1,274 @@
# https://github.com/stripe/stripe-python
# https://stripe.com/docs/api?lang=python#top
from datetime import datetime
from pytz import utc
import stripe
try:
import unittest
from unittest import TestCase
except:
from django.test import TestCase
from django.utils import unittest
# if customer.id doesn't exist, create one and then charge the customer
# we probably should ask our users whether they are ok with our creating a customer id account -- or ask for credit
# card info each time....
# should load the keys for Stripe from db -- but for now just hardcode here
# moving towards not having the stripe api key for the non profit partner in the unglue.it code -- but in a logically
# distinct application
try:
from regluit.core.models import Key
STRIPE_PK = Key.objects.get(name="STRIPE_PK").value
STRIPE_SK = Key.objects.get(name="STRIPE_SK").value
STRIPE_PARTNER_PK = Key.objects.get(name="STRIPE_PARTNER_PK").value
STRIPE_PARTNER_SK = Key.objects.get(name="STRIPE_PARTNER_SK").value
logger.info('Successful loading of STRIPE_*_KEYs')
except Exception, e:
# currently test keys for Gluejar and for raymond.yee@gmail.com as standin for non-profit
STRIPE_PK = 'pk_0EajXPn195ZdF7Gt7pCxsqRhNN5BF'
STRIPE_SK = 'sk_0EajIO4Dnh646KPIgLWGcO10f9qnH'
STRIPE_PARTNER_PK ='pk_0AnIkNu4WRiJYzxMKgruiUwxzXP2T'
STRIPE_PARTNER_SK = 'sk_0AnIvBrnrJoFpfD3YmQBVZuTUAbjs'
# set default stripe api_key to that of unglue.it
stripe.api_key = STRIPE_SK
# https://stripe.com/docs/testing
TEST_CARDS = (
('4242424242424242', 'Visa'),
('4012888888881881', 'Visa'),
('5555555555554444', 'MasterCard'),
('5105105105105100', 'MasterCard'),
('378282246310005', 'American Express'),
('371449635398431', 'American Express'),
('6011111111111117', 'Discover'),
('6011000990139424', 'Discover'),
('30569309025904', "Diner's Club"),
('38520000023237', "Diner's Club"),
('3530111333300000', 'JCB'),
('3566002020360505','JCB')
)
ERROR_TESTING = dict((
('ADDRESS1_ZIP_FAIL', ('4000000000000010', 'address_line1_check and address_zip_check will both fail')),
('ADDRESS1_FAIL', ('4000000000000028', 'address_line1_check will fail.')),
('ADDRESS_ZIP_FAIL', ('4000000000000036', 'address_zip_check will fail.')),
('CVC_CHECK_FAIL', ('4000000000000101', 'cvc_check will fail.')),
('BAD_ATTACHED_CARD', ('4000000000000341', 'Attaching this card to a Customer object will succeed, but attempts to charge the customer will fail.')),
('CHARGE_DECLINE', ('4000000000000002', 'Charges with this card will always be declined.'))
))
# types of errors / when they can be handled
#card_declined: Use this special card number - 4000000000000002.
#incorrect_number: Use a number that fails the Luhn check, e.g. 4242424242424241.
#invalid_expiry_month: Use an invalid month e.g. 13.
#invalid_expiry_year: Use a year in the past e.g. 1970.
#invalid_cvc: Use a two digit number e.g. 99.
def filter_none(d):
return dict([(k,v) for (k,v) in d.items() if v is not None])
# if you create a Customer object, then you'll be able to charge multiple times. You can create a customer with a token.
# https://stripe.com/docs/tutorials/charges
def card (number=TEST_CARDS[0][0], exp_month='01', exp_year='2020', cvc=None, name=None,
address_line1=None, address_line2=None, address_zip=None, address_state=None, address_country=None):
card = {
"number": number,
"exp_month": str(exp_month),
"exp_year": str(exp_year),
"cvc": str(cvc) if cvc is not None else None,
"name": name,
"address_line1": address_line1,
"address_line2": address_line2,
"address_zip": address_zip,
"address_state": address_state,
"address_country": address_country
}
return filter_none(card)
class StripeClient(object):
def __init__(self, api_key=STRIPE_SK):
self.api_key = api_key
# key entities: Charge, Customer, Token, Event
@property
def charge(self):
return stripe.Charge(api_key=self.api_key)
@property
def customer(self):
return stripe.Customer(api_key=self.api_key)
@property
def token(self):
return stripe.Token(api_key=self.api_key)
@property
def transfer(self):
return stripe.Transfer(api_key=self.api_key)
@property
def event(self):
return stripe.Event(api_key=self.api_key)
def create_token(self, card):
return stripe.Token(api_key=self.api_key).create(card=card)
def create_customer (self, card=None, description=None, email=None, account_balance=None, plan=None, trial_end=None):
"""card is a dictionary or a token"""
# https://stripe.com/docs/api?lang=python#create_customer
customer = stripe.Customer(api_key=self.api_key).create(
card=card,
description=description,
email=email,
account_balance=account_balance,
plan=plan,
trial_end=trial_end
)
# customer.id is useful to save in db
return customer
def create_charge(self, amount, currency="usd", customer=None, card=None, description=None ):
# https://stripe.com/docs/api?lang=python#create_charge
# customer or card required but not both
# charge the Customer instead of the card
# amount in cents
charge = stripe.Charge(api_key=self.api_key).create(
amount=int(100*amount), # in cents
currency=currency,
customer=customer.id if customer is not None else None,
card=card,
description=description
)
return charge
def refund_charge(self, charge_id):
# https://stripe.com/docs/api?lang=python#refund_charge
ch = stripe.Charge(api_key=self.api_key).retrieve(charge_id)
ch.refund()
return ch
def list_all_charges(self, count=None, offset=None, customer=None):
# https://stripe.com/docs/api?lang=python#list_charges
return stripe.Charge(api_key=self.api_key).all(count=count, offset=offset, customer=customer)
# what to work through?
# can't test Transfer in test mode: "There are no transfers in test mode."
#pledge scenario
# bad card -- what types of erros to handle?
# https://stripe.com/docs/api#errors
# what errors are handled in the python library and how?
#
# Account?
# https://stripe.com/docs/api#event_types
# events of interest -- especially ones that do not directly arise immediately (synchronously) from something we do -- I think
# especially: charge.disputed
# I think following (charge.succeeded, charge.failed, charge.refunded) pretty much sychronous to our actions
# customer.created, customer.updated, customer.deleted
# transfer
# I expect the ones related to transfers all happen asynchronously: transfer.created, transfer.updated, transfer.failed
# When will the money I charge with Stripe end up in my bank account?
# Every day, we transfer the money that you charged seven days previously?that is, you receive the money for your March 1st charges on March 8th.
# pending payments?
# how to tell whether money transferred to bank account yet
# best practices for calling Events -- not too often.
class PledgeScenarioTest(TestCase):
@classmethod
def setUpClass(cls):
cls._sc = StripeClient(api_key=STRIPE_SK)
# valid card
card0 = card()
cls._good_cust = cls._sc.create_customer(card=card0, description="test good customer", email="raymond.yee@gmail.com")
# bad card
test_card_num_to_get_BAD_ATTACHED_CARD = ERROR_TESTING['BAD_ATTACHED_CARD'][0]
card1 = card(number=test_card_num_to_get_BAD_ATTACHED_CARD)
cls._cust_bad_card = cls._sc.create_customer(card=card1, description="test bad customer", email="rdhyee@gluejar.com")
def test_charge_good_cust(self):
charge = self._sc.create_charge(10, customer=self._good_cust, description="$10 for good cust")
self.assertEqual(type(charge.id), str)
# print out all the pieces of Customer and Charge objects
print dir(charge)
print dir(self._good_cust)
def test_error_creating_customer_with_declined_card(self):
# should get a CardError upon attempt to create Customer with this card
_card = card(number=card(ERROR_TESTING['CHARGE_DECLINE'][0]))
self.assertRaises(stripe.CardError, self._sc.create_customer, card=_card)
def test_charge_bad_cust(self):
# expect the card to be declined -- and for us to get CardError
self.assertRaises(stripe.CardError, self._sc.create_charge, 10,
customer = self._cust_bad_card, description="$10 for bad cust")
@classmethod
def tearDownClass(cls):
# clean up stuff we create in test -- right now list current objects
#cls._good_cust.delete()
print "list of customers"
print [(i, 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) for(i, c) in enumerate(cls._sc.customer.all()["data"])]
print "list of charges"
print [(i, c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid, c.fee, c.disputed, c.amount_refunded, c.failure_message, c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year) for (i, c) in enumerate(cls._sc.charge.all()['data'])]
# can retrieve events since a certain time?
print "list of events", cls._sc.event.all()
print [(i, e.id, e.type, e.created, e.pending_webhooks, e.data) for (i,e) in enumerate(cls._sc.event.all()['data'])]
def suite():
testcases = [PledgeScenarioTest]
#testcases = []
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
#suites.addTest(LibraryThingTest('test_cache'))
#suites.addTest(SettingsTest('test_dev_me_alignment')) # give option to test this alignment
return suites
# IPNs/webhooks: https://stripe.com/docs/webhooks
# how to use pending_webhooks ?
# all events
# https://stripe.com/docs/api?lang=python#list_events
if __name__ == '__main__':
#unittest.main()
suites = suite()
#suites = unittest.defaultTestLoader.loadTestsFromModule(__import__('__main__'))
unittest.TextTestRunner().run(suites)

View File

@ -0,0 +1,38 @@
{% extends "basepledge.html" %}
{% load humanize %}
{% block title %}Stripe{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
<link href="/static/stripe/tag.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/static/stripe/tag.js"></script>
{% endblock %}
{% block doccontent %}
Stripe Test!:
<span class="payment-errors"></span>
<form action="" method="post" id="payment-form">
{% csrf_token %}
<payment key="{{STRIPE_PK}}"></payment>
<input type="submit" value="Submit">
</form>
<script type="application/x-javascript">
var $j = jQuery.noConflict();
console.debug('setting up handlers in stripe.html');
$j('payment').bind('success.payment', function () {
console.debug('success.payment ev');
});
</script>
{% endblock %}

View File

@ -190,7 +190,7 @@ class PledgeTest(TestCase):
# Note, set this to 1-5 different receivers with absolute amounts for each
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)
@ -220,7 +220,7 @@ class PledgeTest(TestCase):
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.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)
@ -244,7 +244,7 @@ class PledgeTest(TestCase):
# Note, set this to 1-5 different receivers with absolute amounts for each
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)
@ -284,7 +284,7 @@ class AuthorizeTest(TestCase):
# 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(t)
self.validateRedirect(t, url)
@ -301,6 +301,31 @@ class AuthorizeTest(TestCase):
def tearDown(self):
self.selenium.quit()
class CreditTest(TestCase):
user1=None
user2=None
def setUp(self):
"""
"""
self.user1 = User.objects.create_user('credit_test1', 'support@gluejar.com', 'credit_test1')
self.user2 = User.objects.create_user('credit_test2', 'support+1@gluejar.com', 'credit_test2')
def testSimple(self):
"""
"""
self.assertFalse(self.user1.credit.add_to_balance(-100))
self.assertTrue(self.user1.credit.add_to_balance(100))
self.assertTrue(self.user1.credit.add_to_pledged(50))
self.assertFalse(self.user1.credit.add_to_pledged(60))
self.assertFalse(self.user1.credit.use_pledge(60))
self.assertTrue(self.user1.credit.use_pledge(50))
self.assertFalse(self.user1.credit.transfer_to(self.user2,60))
self.assertTrue(self.user1.credit.transfer_to(self.user2,50))
self.assertEqual(self.user1.credit.balance, 0)
self.assertEqual(self.user2.credit.balance, 50)
class TransactionTest(TestCase):
def setUp(self):
"""
@ -363,7 +388,7 @@ class BasicGuiTest(TestCase):
def suite():
#testcases = [PledgeTest, AuthorizeTest, TransactionTest]
testcases = [TransactionTest]
testcases = [TransactionTest, CreditTest]
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
return suites

View File

@ -1,16 +1,12 @@
from django.conf.urls.defaults import *
from django.conf import settings
from regluit.payment.views import StripeView
urlpatterns = patterns(
"regluit.payment.views",
url(r"^handleipn/(?P<module>\w+)$", "handleIPN", name="HandleIPN"),
)
# Amazon payment URLs
urlpatterns += patterns(
"regluit.payment.amazon",
url(r"^amazonpaymentreturn", "amazonPaymentReturn", name="AmazonPaymentReturn"),
)
# this should be on only if DEBUG is on
@ -22,12 +18,11 @@ if settings.DEBUG:
url(r"^testexecute", "testExecute"),
url(r"^testcancel", "testCancel"),
url(r"^querycampaign", "queryCampaign"),
url(r"^runtests", "runTests"),
url(r"^paymentcomplete","paymentcomplete"),
url(r"^checkstatus", "checkStatus"),
url(r"^testfinish", "testFinish"),
url(r"^testrefund", "testRefund"),
url(r"^testmodify", "testModify"),
url(r"^stripe/test", StripeView.as_view())
)

View File

@ -1,7 +1,11 @@
from regluit.payment.manager import PaymentManager
from regluit.payment.paypal import IPN
from regluit.payment.models import Transaction
from regluit.core.models import Campaign, Wishlist
from regluit.payment.stripelib import STRIPE_PK
from regluit.payment.forms import StripePledgeForm
from django.conf import settings
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response
@ -13,8 +17,12 @@ from django.views.decorators.csrf import csrf_exempt
from django.test.utils import setup_test_environment
from django.template import RequestContext
from django.views.generic.edit import FormView
from django.views.generic.base import TemplateView
from unittest import TestResult
from regluit.payment.tests import PledgeTest, AuthorizeTest
import uuid
from decimal import Decimal as D
@ -113,12 +121,8 @@ def testAuthorize(request):
receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00},
{'email': TEST_RECEIVERS[1], 'amount':10.00}]
if campaign_id:
campaign = Campaign.objects.get(id=int(campaign_id))
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)
t, url = p.authorize(Transaction.objects.create(currency='USD', max_amount=amount, campaign=campaign, user=None), return_url=None)
if url:
logger.info("testAuthorize: " + url)
@ -249,12 +253,9 @@ def testPledge(request):
else:
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))
t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user, return_url=None)
t, url = p.pledge('USD', 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:
logger.info("testPledge: " + url)
@ -265,33 +266,6 @@ def testPledge(request):
logger.info("testPledge: Error " + str(t.error))
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
def handleIPN(request, module):
@ -304,11 +278,6 @@ def handleIPN(request, module):
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):
# Check the status of all PAY transactions and flag any errors
@ -322,5 +291,25 @@ def checkStatus(request):
def _render(request, template, template_vars={}):
return render_to_response(template, template_vars, RequestContext(request))
class StripeView(FormView):
template_name="stripe.html"
form_class = StripePledgeForm
def get_context_data(self, **kwargs):
context = super(StripeView, self).get_context_data(**kwargs)
context.update({
'STRIPE_PK':STRIPE_PK
})
return context
def form_valid(self, form):
stripe_token = form.cleaned_data["stripe_token"]
# e.g., tok_0C0k4jG5B2Oxox
#
return HttpResponse("stripe_token: {0}".format(stripe_token))

View File

@ -1,4 +1,4 @@
Django==1.3.1
Django==1.4.1
MySQL-python==1.2.3
south
django-extensions
@ -19,8 +19,8 @@ freebase
django-endless-pagination
django-selectable
pytz
django-notification
git+ssh://git@github.com/Gluejar/boto.git@2.3.0
https://github.com/aladagemre/django-notification/tarball/master
fabric
paramiko
pyasn1
@ -28,3 +28,4 @@ pycrypto==2.5
django-maintenancemode
django-smtp-ssl
django-ckeditor
stripe

View File

@ -1,56 +1,52 @@
# requirements.pip does not specify versions of the libraries. Here's a modified version of the output from pip freeze
# on March 7, 2012 for which the tests run successfully for https://github.com/Gluejar/regluit/commit/000d78dbd0e377d2b1688a4a1a333df49a0dd11b
# If you were to run pip install -r requirements.pip anew, the tests no longer run perfectly because of the use of
# some later libraries
Django==1.4.1
Fabric==1.4.3
MySQL-python==1.2.3
Pillow==1.7.7
Pyzotero==0.9.51
South==0.7.6
amqplib==1.0.2
anyjson==0.3.1
boto==2.3.0
celery==2.4.6
certifi==0.0.6
django-celery==2.4.2
anyjson==0.3.3
billiard==2.7.3.12
#boto==2.3.0
git+ssh://git@github.com/Gluejar/boto.git@2.3.0
celery==3.0.9
distribute==0.6.28
django-celery==3.0.9
django-ckeditor==3.6.2.1
django-debug-toolbar==0.8.5
django-endless-pagination==1.1
django-extensions==0.7.1
django-extensions==0.9
django-kombu==0.9.4
django-maintenancemode==0.10
django-nose-selenium==0.7.3
django-notification==0.2
django-picklefield==0.1.9
django-profiles==0.2
https://bitbucket.org/ubernostrum/django-registration/get/tip.tar.gz
#django-registration==0.8-alpha-1
django-selectable==0.2
#django-notification==0.2
git+git://github.com/aladagemre/django-notification.git@2927346f4c513a217ac8ad076e494dd1adbf70e1
django-registration==0.8
django-selectable==0.5.2
django-smtp-ssl==1.0
django-social-auth==0.6.1
#https://github.com/toastdriven/django-tastypie/tarball/master
django-social-auth==0.7.5
django-tastypie==0.9.11
Django==1.3.1
Fabric==1.4.1
feedparser==5.1
feedparser==5.1.2
freebase==1.0.8
httplib2==0.7.2
kombu==1.5.1
# lxml requires special handling to install properly
#lxml==2.3.1
httplib2==0.7.5
kombu==2.4.5
lxml==3.0alpha2
mechanize==0.2.5
mimeparse==0.1.3
MySQL-python==1.2.3
nose==1.1.2
oauth2==1.5.211
paramiko==1.7.7.1
pyasn1==0.1.3
pycrypto==2.5
pyparsing==1.5.6
python-dateutil==1.5
python-digest==1.7
paramiko==1.7.7.2
pyasn1==0.1.4
pycrypto==2.6
python-dateutil==2.1
python-openid==2.2.5
pytz==2012b
Pyzotero==0.9.4
rdflib==3.1.0
redis==2.4.11
requests==0.9.1
selenium==2.24.0
South==0.7.3
ssh==1.7.13
pytz==2012d
rdflib==2.4.0
redis==2.6.2
requests==0.14.0
selenium==2.25.0
six==1.2.0
ssh==1.7.14
stripe==1.7.4
virtualenv==1.4.9
virtualenvwrapper==2.2.2
wsgiref==0.1.2

View File

@ -49,7 +49,7 @@ STATIC_URL = '/static/'
# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
# ADMIN_MEDIA_PREFIX = '/static/admin/'
# Additional locations of static files
STATICFILES_DIRS = (
@ -271,12 +271,8 @@ EBOOK_NOTIFICATIONS_JOB = {
# by default, in common, we don't turn any of the celerybeat jobs on -- turn them on in the local settings file
# set -- sandbox or production Amazon FPS?
AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
#AMAZON_FPS_HOST = "fps.amazonaws.com"
# amazon or paypal for now.
PAYMENT_PROCESSOR = 'amazon'
PAYMENT_PROCESSOR = 'test'
# a SECRET_KEY to be used for encrypting values in core.models.Key -- you should store in settings/local.py
SECRET_KEY = ''
@ -286,3 +282,8 @@ SECRET_KEY = ''
MAINTENANCE_MODE = False
# Sequence of URL path regexes to exclude from the maintenance mode.
MAINTENANCE_IGNORE_URLS = {}
class NONPROFIT:
is_on = True
name = 'Library Renewal'
link = 'http://127.0.0.1:8000/donate_to_campaign/'

View File

@ -102,8 +102,8 @@
border-bottom-width: 1px;
border-top-style: solid;
border-bottom-style: solid;
border-top-color: #FFFFFF;
border-bottom-color: #FFFFFF;
border-top-color: #FFF;
border-bottom-color: #FFF;
}
.panelhoverlink {
text-decoration: none;
@ -113,7 +113,7 @@
width: 118px;
height: 35px;
padding: 0px 0px;
background: #FFFFFF;
background: #FFF;
margin: 0px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
@ -178,6 +178,9 @@
color: #3d4e53;
overflow: hidden;
}
.panelview.book-name div a {
color: #6994a3;
}
.panelview.booklist-status {
display: none;
}
@ -276,7 +279,7 @@ div.panelview.side2 {
width: 118px;
height: 35px;
padding: 0px 0px;
background: #FFFFFF;
background: #FFF;
margin: 0px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
@ -330,7 +333,7 @@ div.panelview.side2 {
width: 118px;
height: 35px;
padding: 0px 0px;
background: #FFFFFF;
background: #FFF;
margin: 0px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
@ -356,7 +359,7 @@ div.panelview.side2 {
width: 118px;
height: 35px;
padding: 0px 0px;
background: #FFFFFF;
background: #FFF;
margin: 0px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
@ -388,8 +391,8 @@ div.panelview.side2 {
border-bottom-width: 1px;
border-top-style: solid;
border-bottom-style: solid;
border-top-color: #FFFFFF;
border-bottom-color: #FFFFFF;
border-top-color: #FFF;
border-bottom-color: #FFF;
background: url("/static/images/book-panel/add_wish_icon.png") no-repeat left center;
padding-right: 0;
}
@ -423,8 +426,8 @@ div.panelview.side2 {
border-bottom-width: 1px;
border-top-style: solid;
border-bottom-style: solid;
border-top-color: #FFFFFF;
border-bottom-color: #FFFFFF;
border-top-color: #FFF;
border-bottom-color: #FFF;
background: url("/static/images/booklist/remove-wishlist-white.png") no-repeat left center;
}
.moreinfo.remove-wishlist a,
@ -453,8 +456,8 @@ div.panelview.side2 {
border-bottom-width: 1px;
border-top-style: solid;
border-bottom-style: solid;
border-top-color: #FFFFFF;
border-bottom-color: #FFFFFF;
border-top-color: #FFF;
border-bottom-color: #FFF;
background: url("/static/images/checkmark_small-white.png") no-repeat left center;
}
.moreinfo.on-wishlist a,
@ -482,7 +485,7 @@ div.panelview.side2 {
margin: 0px;
}
.white_text a {
color: #FFFFFF;
color: #FFF;
text-decoration: none;
}
.white_text a:hover {
@ -505,8 +508,8 @@ div.panelview.side2 {
border-bottom-width: 1px;
border-top-style: solid;
border-bottom-style: solid;
border-top-color: #FFFFFF;
border-bottom-color: #FFFFFF;
border-top-color: #FFF;
border-bottom-color: #FFF;
background: url("/static/images/book-panel/more_icon.png") no-repeat left center;
cursor: pointer;
}

View File

@ -98,7 +98,7 @@
input[type="submit"] {
float: right;
font-size: 19px;
margin: 10px;
margin: 10px 0 10px;
cursor: pointer;
}
.pledge_amount {
@ -149,7 +149,7 @@ p {
}
#fakepledgesubmit {
background-color: #e35351;
cursor: pointer;
cursor: default;
font-weight: bold;
font-size: 19px;
display: none;
@ -157,3 +157,59 @@ p {
span.menu-item-price {
float: none !important;
}
#mandatory_premiums {
font-size: 15px;
}
#mandatory_premiums div {
float: left;
}
#mandatory_premiums div.ack_level {
width: 16%;
margin-right: 3%;
height: 100%;
padding: 1%;
}
#mandatory_premiums div.ack_header {
width: 73%;
padding: 1%;
}
#mandatory_premiums div.ack_active,
#mandatory_premiums div.ack_inactive {
width: 100%;
font-size: 13px;
}
#mandatory_premiums div.ack_active .ack_header,
#mandatory_premiums div.ack_active .ack_level {
border: solid #3d4e53;
border-width: 1%;
background: white;
}
#mandatory_premiums div.ack_inactive .ack_header,
#mandatory_premiums div.ack_inactive .ack_level {
border: solid #d6dde0;
border-width: 1%;
background: #d6dde0;
}
#mandatory_premiums div.ack_inactive input,
#mandatory_premiums div.ack_inactive textarea {
background: #d6dde0;
border: dashed 1px #3d4e53;
}
#mandatory_premiums > div {
margin: 7px 0;
}
#mandatory_premiums input[type=text],
#mandatory_premiums textarea {
width: 95%;
font-size: 15px;
color: #3d4e53;
margin: 5px 0;
}
#mandatory_premiums input[type=text] {
height: 19.5px;
line-height: 19.5px;
}
#id_ack_link {
border: none;
cursor: default;
}

View File

@ -1,19 +0,0 @@
var $j = jQuery.noConflict();
//give pledge box focus
$j(function() {
$j('#id_preapproval_amount').focus();
});
// This autofills the pledge box when users select a premium tier.
$j().ready(function() {
var inputbox = $j('#id_preapproval_amount');
$j('#premiums_list input').on("click", function() {
amount = $j(this).siblings('span.menu-item-price').html();
amount = amount.split('$')[1];
amount = parseInt(amount);
current = inputbox.val();
if (current<amount) {
inputbox.val(amount);
}
});
});

View File

@ -4,47 +4,154 @@ $j(function() {
$j('#id_preapproval_amount').focus();
});
// if amount in pledge box is too small to qualify for premium, call attention to it
// and disable the input button with a helpful message
// when they fix it, revert to original styling and reactivate button
$j().ready(function() {
// cache these to speed things up
var inputbox = $j('#id_preapproval_amount');
var submitbutton = $j('#pledgesubmit');
var fakesubmitbutton = $j('#fakepledgesubmit');
var anonbox = $j('#anonbox input');
var ackSection = $j('#ack_section');
var supporterName = $j('#pass_supporter_name').html();
var ackName = $j('#pass_ack_name').html();
var ackLink = $j('#pass_ack_link').html();
var ackDedication = $j('#pass_ack_dedication').html();
if(ackDedication == 'None') {
ackDedication = '';
}
var acks = {
ack_name: ackName,
ack_link: ackLink,
ack_dedication: ackDedication
};
var ackAnon = $j('#pass_anon').html();
// we're not letting people submit arbitrary links
$j('#id_ack_link').attr('disabled', 'disabled');
// take an input button from the premiums list
// find the premium amount in its associated span class
// convert to usable integer form and return
var canonicalize = function(amt) {
// takes an input button from the premiums list
// finds the premium amount its associated the span class
// converts to usable integer form and returns
amt = amt.siblings('span.menu-item-price').html();
amt = amt.split('$')[1];
amt = parseInt(amt);
return amt;
}
// if amount in pledge box is too small to qualify for premium, highlight pledge
// box and submit button in alert color and disable the pledge input button
// with a helpful message
var mayday = function() {
// highlights pledge box and submit button in alert color
// disables submit button and overwrites with help text
inputbox.css({'border-color': '#e35351', 'background-color': '#e35351', 'color': 'white'});
fakesubmitbutton.val("You must pledge at least $"+amount+" for that premium");
fakesubmitbutton.val("Pledge at least $"+amount+" to claim that premium");
fakesubmitbutton.show();
submitbutton.hide();
submitbutton.attr('disabled', 'disabled');
}
// when pledge covers premium again, revert to original styling and reactivate button
var allclear = function() {
// returns pledge box and submit button to conventional colors
// enables submit button and rewrites with original text
inputbox.css({'border-color': '#8dc63f', 'background-color': 'white', 'color': '#3d4e53'});
fakesubmitbutton.hide();
submitbutton.show();
submitbutton.removeAttr('disabled');
}
$j('#premiums_list input').on("click", function() {
// make acknowledgements input area active
var activate = function(mySpan) {
$j('#'+mySpan).removeClass('ack_inactive').addClass('ack_active');
$j('#'+mySpan+' input').removeAttr('disabled');
ack = acks[mySpan];
$j('#id_'+mySpan).val(ack);
}
// make mandatory premium input area inactive: greyed-out and not modifiable
var deactivate = function(mySpan) {
$j('#'+mySpan).removeClass('ack_active').addClass('ack_inactive');
$j('#'+mySpan+' input[type=text]').val('').attr('disabled', 'disabled');
}
// fill mandatory premium link input with supporter page
var activateLink = function() {
$j('#ack_link').removeClass('ack_inactive').addClass('ack_active');
if(ackLink) {
$j('input#id_ack_link').val(ackLink);
} else {
$j('input#id_ack_link').val('https://unglue.it/supporter/'+supporterName);
}
}
// empty mandatory premium link
var deactivateLink = function() {
$j('#ack_link').removeClass('ack_active').addClass('ack_inactive');
$j('input#id_ack_link').val('');
}
var anonymizeName = function() {
deactivate('ack_name');
$j('#id_ack_name').val('Anonymous');
// clicking the anonbox should disable name field, but not render
// anonbox impossible to click again!
$j('#id_anonymous').removeAttr('disabled');
}
// selectively highlight/grey out acknowledgements supporter is eligible for
var rectifyAcknowledgements = function(current) {
var anon = anonbox.prop("checked");
if (current < 25) {
deactivate('ack_name');
deactivateLink();
deactivate('ack_dedication');
ackSection.html('');
} else if (current >= 25 && current < 50) {
deactivateLink();
deactivate('ack_dedication');
if (anon) {
anonymizeName();
} else {
activate('ack_name');
}
ackSection.html(' as a Supporter');
} else if (current >= 50 && current < 100) {
deactivate('ack_dedication');
if (anon) {
anonymizeName();
deactivateLink();
} else {
activate('ack_name');
activateLink();
}
ackSection.html(' as a Benefactor');
} else if (current >= 100) {
activate('ack_dedication');
if (anon) {
anonymizeName();
deactivateLink();
} else {
activate('ack_name');
activateLink();
}
ackSection.html(' as a Bibliophile');
}
}
// initialize the acknowledgements fields. if we've prefilled the pledge info,
// use that.
current = inputbox.val();
if (current) {
rectifyAcknowledgements(current);
} else {
rectifyAcknowledgements(0);
}
if (ackAnon == 'True') {
anonymizeName();
deactivateLink();
anonbox.prop("checked", true);
}
// when user clicks a premium, ensure it is compatible with the pledge box amount
$j('#premiums_list input').on("click", function() {
amount = canonicalize($j(this));
current = inputbox.val();
if (current<amount) {
@ -54,9 +161,9 @@ $j().ready(function() {
}
});
inputbox.keyup(function() {
// when user changes the pledge box contents, ensure they are compatible
// with the selected pledge
inputbox.keyup(function() {
current = $j(this).val();
if (current[0] == '$') {
@ -65,11 +172,41 @@ $j().ready(function() {
$j(this).val(current);
}
try {
amount = canonicalize($j('input[type=radio]:checked'));
if (current<amount) {
} catch(error) {
amount = 0;
}
if (current<amount && amount !=0) {
mayday();
} else if (submitbutton.attr('disabled')) {
allclear();
}
rectifyAcknowledgements(current);
});
// when supporter clicks the anonymity box, change name & link field to
// display whatever acks page will display: WYSIWYG
anonbox.change(function() {
rectifyAcknowledgements(current);
});
// if supporters enter a name or dedication, keep track of them
// so they doesn't get thrown away if they decrease & re-increase pledge, or
// anonymize and then de-anonymize
$j('#ack_name input[type=text]').change(function() {
acks['ack_name'] = $j(this).val();
});
$j('#ack_dedication input[type=text]').change(function() {
acks['ack_dedication'] = $j(this).val();
});
// input boxes must be enabled for values to be submitted
// we may have disabled them to prevent users from entering non-permitted values
// so re-enable all on submit
submitbutton.on("click", function() {
$j('#mandatory_premiums input').removeAttr('disabled');
});
});

View File

@ -23,8 +23,8 @@
border-bottom-width: 1px;
border-top-style: solid;
border-bottom-style: solid;
border-top-color: #FFFFFF;
border-bottom-color: #FFFFFF;
border-top-color: #FFF;
border-bottom-color: #FFF;
}
.panelhoverlink {
@ -36,7 +36,7 @@
width:118px;
height:35px;
padding:0px 0px;
background:#FFFFFF;
background:#FFF;
margin:0px;
.one-border-radius(4px);
border: 1px solid #81bb38;
@ -127,8 +127,12 @@
font-size: 12px;
line-height:16px;
max-height:32px;
color: #3d4e53;
color: @text-blue;
overflow: hidden;
a {
color: @medium-blue;
}
}
.panelview.booklist-status {
@ -280,7 +284,7 @@ div.panelview.side2 {
margin:0px;
a {
color:#FFFFFF;
color:#FFF;
text-decoration:none;
&:hover { .panelhoverlink;}

View File

@ -8,7 +8,7 @@
input[type="submit"] {
float: right;
font-size: @font-size-header;
margin: 10px;
margin: 10px 0 10px;
cursor: pointer;
}
@ -68,7 +68,7 @@ p {
#fakepledgesubmit {
background-color: @alert;
cursor: pointer;
cursor: default;
font-weight: bold;
font-size: @font-size-header;
display: none;
@ -77,3 +77,69 @@ p {
span.menu-item-price {
float: none !important;
}
#mandatory_premiums {
font-size: @font-size-larger;
div {
float: left;
&.ack_level {
width: 16%;
margin-right: 3%;
height: 100%;
padding: 1%;
}
&.ack_header {
width: 73%;
padding: 1%;
}
&.ack_active, &.ack_inactive {
width: 100%;
font-size: @font-size-default;
}
&.ack_active {
.ack_header, .ack_level {
border: solid @text-blue;
border-width: 1%;
background: white;
}
}
&.ack_inactive {
.ack_header, .ack_level {
border: solid @blue-grey;
border-width: 1%;
background: @blue-grey;
}
input, textarea {
background: @blue-grey;
border: dashed 1px @text-blue;
}
}
}
> div {
margin: 7px 0;
}
input[type=text], textarea {
width: 95%;
font-size: @font-size-larger;
color: @text-blue;
margin: 5px 0;
}
input[type=text] {
.height(@font-size-larger*1.3);
}
}
#id_ack_link {
border: none;
cursor: default;
}

View File

@ -13,3 +13,80 @@
border-style: solid none;
border-color: #FFFFFF;
}
.roundedspan {
border: 1px solid #d4d4d4;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
padding: 1px;
color: #fff;
margin: 0 8px 0 0;
display: inline-block;
}
.roundedspan > span {
padding: 7px 7px;
min-width: 15px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
text-align: center;
display: inline-block;
}
.roundedspan > span .hovertext {
display: none;
}
.roundedspan > span:hover .hovertext {
display: inline;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 24px;
line-height: 24px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
height: 24px;
width: 24px;
}
.actionbuttons {
width: auto;
height: 36px;
line-height: 36px;
background: #8dc63f;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
color: white;
cursor: pointer;
font-size: 13px;
font-weight: bold;
padding: 0 15px;
border: none;
margin: 5px 0;
}
.errors {
-moz-border-radius: 16px 16px 0 0;
-webkit-border-radius: 16px 16px 0 0;
border-radius: 16px 16px 0 0;
border: solid #e35351 3px;
clear: both;
width: 90%;
height: auto;
line-height: 16px;
padding: 7px 0;
font-weight: bold;
font-size: 13px;
text-align: center;
}
.errors li {
list-style: none;
border: none;
}

58
static/stripe/tag.css Executable file
View File

@ -0,0 +1,58 @@
payment, .payment {
position: relative;
display: block;
padding: 15px 20px;
max-width: 300px;
overflow: hidden;
box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-sizing: border-box;
}
payment label, .payment label {
display: block;
padding: 5px 0;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
payment input, .payment input {
padding: 5px 5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-sizing: border-box;
}
payment .number input, .payment .number input {
padding: 7px 40px 7px 7px;
width: 100%;
}
payment .expiry input, payment .cvc input {
width: 45px;
}
payment .expiry em {
display: none;
}
payment .cvc,
.payment .cvc {
float: right;
text-align: right;
}
payment .expiry,
.payment .expiry {
float: left;
}
payment .message,
.payment .message {
display: block;
}

405
static/stripe/tag.dev.js Executable file
View File

@ -0,0 +1,405 @@
(function() {
var $, global, script,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__slice = [].slice;
$ = this.jQuery || this.Zepto;
if (!$) {
throw 'jQuery/Zepto required';
}
this.PaymentTag = (function() {
PaymentTag.replaceTags = function(element) {
var _this = this;
if (element == null) {
element = document.body;
}
return $('payment, .payment-tag', element).each(function(i, tag) {
return new _this({
el: tag
}).render();
});
};
PaymentTag.prototype.defaults = {
tokenName: 'stripe_token',
token: true,
cvc: true
};
function PaymentTag(options) {
var _ref, _ref1;
if (options == null) {
options = {};
}
this.changeCardType = __bind(this.changeCardType, this);
this.restrictNumeric = __bind(this.restrictNumeric, this);
this.formatNumber = __bind(this.formatNumber, this);
this.handleToken = __bind(this.handleToken, this);
this.submit = __bind(this.submit, this);
this.$el = options.el || '<payment />';
this.$el = $(this.$el);
options.key || (options.key = this.$el.attr('key') || this.$el.attr('data-key'));
if ((_ref = options.cvc) == null) {
options.cvc = !((this.$el.attr('nocvc') != null) || (this.$el.attr('data-nocvc') != null));
}
if ((_ref1 = options.token) == null) {
options.token = !((this.$el.attr('notoken') != null) || (this.$el.attr('data-notoken') != null));
}
options.form || (options.form = this.$el.parents('form'));
this.options = $.extend({}, this.defaults, options);
if (this.options.key) {
this.setKey(this.options.key);
}
this.setForm(this.options.form);
this.$el.delegate('.number input', 'keydown', this.formatNumber);
this.$el.delegate('.number input', 'keyup', this.changeCardType);
this.$el.delegate('input[type=tel]', 'keypress', this.restrictNumeric);
}
PaymentTag.prototype.render = function() {
this.$el.html(this.constructor.view(this));
this.$number = this.$('.number input');
this.$cvc = this.$('.cvc input');
this.$expiryMonth = this.$('.expiry input.expiryMonth');
this.$expiryYear = this.$('.expiry input.expiryYear');
this.$message = this.$('.message');
return this;
};
PaymentTag.prototype.renderToken = function(token) {
this.$token = $('<input type="hidden">');
this.$token.attr('name', this.options.tokenName);
this.$token.val(token);
return this.$el.html(this.$token);
};
PaymentTag.prototype.setForm = function($form) {
this.$form = $($form);
return this.$form.bind('submit.payment', this.submit);
};
PaymentTag.prototype.setKey = function(key) {
this.key = key;
return Stripe.setPublishableKey(this.key);
};
PaymentTag.prototype.validate = function() {
var expiry, valid;
valid = true;
this.$('div').removeClass('invalid');
this.$message.empty();
if (!Stripe.validateCardNumber(this.$number.val())) {
valid = false;
this.handleError({
code: 'invalid_number'
});
}
expiry = this.expiryVal();
if (!Stripe.validateExpiry(expiry.month, expiry.year)) {
valid = false;
this.handleError({
code: 'expired_card'
});
}
if (this.options.cvc && !Stripe.validateCVC(this.$cvc.val())) {
valid = false;
this.handleError({
code: 'invalid_cvc'
});
}
if (!valid) {
this.$('.invalid input:first').select();
}
return valid;
};
PaymentTag.prototype.createToken = function(callback) {
var complete, expiry,
_this = this;
complete = function(status, response) {
if (response.error) {
return callback(response.error);
} else {
return callback(null, response);
}
};
expiry = this.expiryVal();
return Stripe.createToken({
number: this.$number.val(),
cvc: this.$cvc.val() || null,
exp_month: expiry.month,
exp_year: expiry.year
}, complete);
};
PaymentTag.prototype.submit = function(e) {
if (e != null) {
e.preventDefault();
}
if (e != null) {
e.stopImmediatePropagation();
}
if (!this.validate()) {
return;
}
if (this.pending) {
return;
}
this.pending = true;
this.disableInputs();
this.trigger('pending');
this.$el.addClass('pending');
return this.createToken(this.handleToken);
};
PaymentTag.prototype.handleToken = function(err, response) {
this.enableInputs();
this.trigger('complete');
this.$el.removeClass('pending');
this.pending = false;
if (err) {
return this.handleError(err);
} else {
this.trigger('success', response);
this.$el.addClass('success');
if (this.options.token) {
this.renderToken(response.id);
}
this.$form.unbind('submit.payment', this.submit);
return this.$form.submit();
}
};
PaymentTag.prototype.formatNumber = function(e) {
var digit, lastDigits, value;
digit = String.fromCharCode(e.which);
if (!/^\d+$/.test(digit)) {
return;
}
value = this.$number.val();
if (Stripe.cardType(value) === 'American Express') {
lastDigits = value.match(/^(\d{4}|\d{4}\s\d{6})$/);
} else {
lastDigits = value.match(/(?:^|\s)(\d{4})$/);
}
if (lastDigits) {
return this.$number.val(value + ' ');
}
};
PaymentTag.prototype.restrictNumeric = function(e) {
var char;
if (e.shiftKey || e.metaKey) {
return true;
}
if (e.which === 0) {
return true;
}
char = String.fromCharCode(e.which);
return !/[A-Za-z]/.test(char);
};
PaymentTag.prototype.cardTypes = {
'Visa': 'visa',
'American Express': 'amex',
'MasterCard': 'mastercard',
'Discover': 'discover',
'Unknown': 'unknown'
};
PaymentTag.prototype.changeCardType = function(e) {
var map, name, type, _ref;
type = Stripe.cardType(this.$number.val());
if (!this.$number.hasClass(type)) {
_ref = this.cardTypes;
for (name in _ref) {
map = _ref[name];
this.$number.removeClass(map);
}
return this.$number.addClass(this.cardTypes[type]);
}
};
PaymentTag.prototype.handleError = function(err) {
if (err.message) {
this.$message.text(err.message);
}
switch (err.code) {
case 'card_declined':
this.invalidInput(this.$number);
break;
case 'invalid_number':
case 'incorrect_number':
this.invalidInput(this.$number);
break;
case 'invalid_expiry_month':
this.invalidInput(this.$expiryMonth);
break;
case 'invalid_expiry_year':
case 'expired_card':
this.invalidInput(this.$expiryYear);
break;
case 'invalid_cvc':
this.invalidInput(this.$cvc);
}
this.$('label.invalid:first input').select();
this.trigger('error', err);
return typeof console !== "undefined" && console !== null ? console.error('Stripe error:', err) : void 0;
};
PaymentTag.prototype.invalidInput = function(input) {
input.parent().addClass('invalid');
return this.trigger('invalid', [input.attr('name'), input]);
};
PaymentTag.prototype.expiryVal = function() {
var month, prefix, trim, year;
trim = function(s) {
return s.replace(/^\s+|\s+$/g, '');
};
month = trim(this.$expiryMonth.val());
year = trim(this.$expiryYear.val());
if (year.length === 2) {
prefix = (new Date).getFullYear();
prefix = prefix.toString().slice(0, 2);
year = prefix + year;
}
return {
month: month,
year: year
};
};
PaymentTag.prototype.enableInputs = function() {
var $elements;
$elements = this.$el.add(this.$form).find(':input');
return $elements.each(function() {
var $item, _ref;
$item = $(this);
return $elements.attr('disabled', (_ref = $item.data('olddisabled')) != null ? _ref : false);
});
};
PaymentTag.prototype.disableInputs = function() {
var $elements;
$elements = this.$el.add(this.$form).find(':input');
return $elements.each(function() {
var $item;
$item = $(this);
$item.data('olddisabled', $item.attr('disabled'));
return $item.attr('disabled', true);
});
};
PaymentTag.prototype.trigger = function() {
var data, event, _ref;
event = arguments[0], data = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return (_ref = this.$el).trigger.apply(_ref, ["" + event + ".payment"].concat(__slice.call(data)));
};
PaymentTag.prototype.$ = function(sel) {
return $(sel, this.$el);
};
return PaymentTag;
})();
document.createElement('payment');
if (typeof module !== "undefined" && module !== null) {
module.exports = PaymentTag;
}
global = this;
if (global.Stripe) {
$(function() {
return typeof PaymentTag.replaceTags === "function" ? PaymentTag.replaceTags() : void 0;
});
} else {
script = document.createElement('script');
script.onload = script.onreadystatechange = function() {
if (!global.Stripe) {
return;
}
if (script.done) {
return;
}
script.done = true;
return typeof PaymentTag.replaceTags === "function" ? PaymentTag.replaceTags() : void 0;
};
script.src = 'https://js.stripe.com/v1/';
$(function() {
var sibling;
sibling = document.getElementsByTagName('script')[0];
return sibling != null ? sibling.parentNode.insertBefore(script, sibling) : void 0;
});
}
}).call(this);
(function() {
this.PaymentTag || (this.PaymentTag = {});
this.PaymentTag["view"] = function(__obj) {
if (!__obj) __obj = {};
var __out = [], __capture = function(callback) {
var out = __out, result;
__out = [];
callback.call(this);
result = __out.join('');
__out = out;
return __safe(result);
}, __sanitize = function(value) {
if (value && value.ecoSafe) {
return value;
} else if (typeof value !== 'undefined' && value != null) {
return __escape(value);
} else {
return '';
}
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
__safe = __obj.safe = function(value) {
if (value && value.ecoSafe) {
return value;
} else {
if (!(typeof value !== 'undefined' && value != null)) value = '';
var result = new String(value);
result.ecoSafe = true;
return result;
}
};
if (!__escape) {
__escape = __obj.escape = function(value) {
return ('' + value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
}
(function() {
(function() {
__out.push('<span class="message"></span>\n\n<div class="number">\n <label for="paymentNumber">Card number</label>\n\n <input type="tel" id="paymentNumber" placeholder="4242 4242 4242 4242" autofocus required>\n</div>\n\n<div class="expiry">\n <label for="paymentExpiryMonth">Expiry date <em>(mm/yy)</em></label>\n\n <input class="expiryMonth" type="tel" id="paymentExpiryMonth" placeholder="mm" required>\n <input class="expiryYear" type="tel" id="paymentExpiryYear" placeholder="yy" required>\n</div>\n\n');
if (this.options.cvc) {
__out.push('\n <div class="cvc">\n <label for="paymentCVC">Security code</label>\n <input type="tel" id="paymentCVC" placeholder="123" maxlength="4" required>\n </div>\n');
}
__out.push('\n');
}).call(this);
}).call(__obj);
__obj.safe = __objSafe, __obj.escape = __escape;
return __out.join('');
};
}).call(this);

1
static/stripe/tag.js Executable file

File diff suppressed because one or more lines are too long

BIN
static/stripe/themes/amex.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/stripe/themes/discover.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
static/stripe/themes/generic.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
static/stripe/themes/spinner.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

152
static/stripe/themes/stripe.css Executable file
View File

@ -0,0 +1,152 @@
payment, .payment {
position: relative;
display: block;
border-radius: 5px;
padding: 15px 20px;
max-width: 300px;
overflow: hidden;
box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-sizing: border-box;
font-size: 12px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif;
background: #FFF;
background-image: -o-linear-gradient(#FFF, #F9FAFA);
background-image: -ms-linear-gradient(#FFF, #F9FAFA);
background-image: -moz-linear-gradient(#FFF, #F9FAFA);
background-image: -webkit-linear-gradient(#FEFEFE, #F9FAFA);
background-image: linear-gradient(#FFF, #F9FAFA);
-moz-box-shadow: 0 0 2px rgba(80,84,92,0.3), 0 1px 1px rgba(80,84,92,0.5);
-webkit-box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
-ms-box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
}
payment ::-webkit-input-placeholder,
.payment ::-webkit-input-placeholder {
text-transform: uppercase;
}
payment label, .payment label {
display: block;
color: #999;
font-size: 13px;
padding: 5px 0;
text-transform: uppercase;
text-shadow: 0 1px 0 #FFF;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
payment input, .payment input {
font-size: 13px;
padding: 5px 5px;
border: 1px solid #BBB;
border-top-color: #999;
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
border-radius: 3px;
-webkit-transition: -webkit-box-shadow 0.1s ease-in-out;
-moz-transition: -moz-box-shadow 0.1s ease-in-out;
transition: -moz-box-shadow 0.1s ease-in-out;
box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-sizing: border-box;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif;
font-size: 14px;
}
payment input:focus, .payment input:focus {
border: 1px solid #5695DB;
outline: none;
-webkit-box-shadow: inset 0 1px 2px #DDD, 0px 0 5px #5695DB;
-moz-box-shadow: 0 0 5px #5695db;
box-shadow: inset 0 1px 2px #DDD, 0px 0 5px #5695DB;
}
payment .invalid input, .payment .invalid input {
outline: none;
border-color: rgba(255, 0, 0, 0.5);
-moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
-ms-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
}
payment input:disabled, .payment input:disabled {
opacity: 0.5;
}
payment .number,
.payment .number {
margin-bottom: 8px;
}
payment .number input, .payment .number input {
padding: 7px 40px 7px 7px;
background: #FFF url(generic.png) 98.5% 20% no-repeat;
width: 100%;
}
payment .number input.visa, .payment .number input.visa {
background-image: url(visa.png);
}
payment .number input.mastercard, .payment .number input.mastercard {
background-image: url(mastercard.png);
}
payment .number input.discover, .payment .number input.discover {
background-image: url(discover.png);
}
payment .number input.amex, .payment .number input.amex {
background-image: url(amex.png);
}
payment .expiry input, payment .cvc input {
width: 45px;
}
payment .expiry em {
font-size: 10px;
font-style: normal;
display: none;
}
payment .cvc,
.payment .cvc {
float: right;
text-align: right;
}
payment .expiry,
.payment .expiry {
float: left;
}
payment .message,
.payment .message {
display: block;
}
payment.pending,
.payment.pending,
payment.success,
.payment.success {
background: #FFF url(spinner.gif) center center no-repeat;
min-height: 130px;
}

BIN
static/stripe/themes/visa.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB