Merge branch 'relaunch'
commit
2260437cfc
|
@ -82,5 +82,24 @@
|
||||||
"description": "Your username, profile URL, and profile tagline under \"bibliophiles\"",
|
"description": "Your username, profile URL, and profile tagline under \"bibliophiles\"",
|
||||||
"created": "2011-11-17 22:03:37"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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']
|
|
@ -111,6 +111,16 @@ class Premium(models.Model):
|
||||||
t_model=get_model('payment','Transaction')
|
t_model=get_model('payment','Transaction')
|
||||||
return self.limit - t_model.objects.filter(premium=self).count()
|
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):
|
class CampaignAction(models.Model):
|
||||||
timestamp = models.DateTimeField(auto_now_add=True)
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -131,8 +141,8 @@ class CCLicense():
|
||||||
)
|
)
|
||||||
CHOICES = CCCHOICES+(('PD-US', 'Public Domain, US'),)
|
CHOICES = CCCHOICES+(('PD-US', 'Public Domain, US'),)
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def url(klass, license):
|
def url(license):
|
||||||
if license == 'PD-US':
|
if license == 'PD-US':
|
||||||
return 'http://creativecommons.org/publicdomain/mark/1.0/'
|
return 'http://creativecommons.org/publicdomain/mark/1.0/'
|
||||||
elif license == 'CC0':
|
elif license == 'CC0':
|
||||||
|
@ -152,8 +162,8 @@ class CCLicense():
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def badge(klass,license):
|
def badge(license):
|
||||||
if license == 'PD-US':
|
if license == 'PD-US':
|
||||||
return 'https://i.creativecommons.org/p/mark/1.0/88x31.png'
|
return 'https://i.creativecommons.org/p/mark/1.0/88x31.png'
|
||||||
elif license == 'CC0':
|
elif license == 'CC0':
|
||||||
|
@ -264,7 +274,12 @@ class Campaign(models.Model):
|
||||||
|
|
||||||
def transactions(self, **kwargs):
|
def transactions(self, **kwargs):
|
||||||
p = PaymentManager()
|
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):
|
def activate(self):
|
||||||
|
@ -368,6 +383,35 @@ class Campaign(models.Model):
|
||||||
|
|
||||||
return ungluers
|
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):
|
def effective_premiums(self):
|
||||||
"""returns the available premiums for the Campaign including any default premiums"""
|
"""returns the available premiums for the Campaign including any default premiums"""
|
||||||
q = Q(campaign=self) | Q(campaign__isnull=True)
|
q = Q(campaign=self) | Q(campaign__isnull=True)
|
||||||
|
@ -375,7 +419,7 @@ class Campaign(models.Model):
|
||||||
|
|
||||||
def custom_premiums(self):
|
def custom_premiums(self):
|
||||||
"""returns only the active custom premiums for the Campaign"""
|
"""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
|
@property
|
||||||
def rightsholder(self):
|
def rightsholder(self):
|
||||||
|
@ -428,8 +472,8 @@ class Identifier(models.Model):
|
||||||
other.delete()
|
other.delete()
|
||||||
return identifier
|
return identifier
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def get_or_add(klass, type='goog', value=None, edition=None, work=None):
|
def get_or_add( type='goog', value=None, edition=None, work=None):
|
||||||
try:
|
try:
|
||||||
return Identifier.objects.get(type=type, value=value)
|
return Identifier.objects.get(type=type, value=value)
|
||||||
except Identifier.DoesNotExist:
|
except Identifier.DoesNotExist:
|
||||||
|
@ -652,6 +696,13 @@ class Work(models.Model):
|
||||||
return edition.publication_date
|
return edition.publication_date
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def publication_date_year(self):
|
||||||
|
try:
|
||||||
|
return self.publication_date[:4]
|
||||||
|
except IndexError:
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
@ -752,8 +803,8 @@ class Edition(models.Model):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def get_by_isbn(klass, isbn):
|
def get_by_isbn( isbn):
|
||||||
if len(isbn)==10:
|
if len(isbn)==10:
|
||||||
isbn=regluit.core.isbn.convert_10_to_13(isbn)
|
isbn=regluit.core.isbn.convert_10_to_13(isbn)
|
||||||
try:
|
try:
|
||||||
|
@ -791,8 +842,8 @@ class Ebook(models.Model):
|
||||||
return CCLicense.badge('PD-US')
|
return CCLicense.badge('PD-US')
|
||||||
return CCLicense.badge(self.rights)
|
return CCLicense.badge(self.rights)
|
||||||
|
|
||||||
@classmethod
|
@staticmethod
|
||||||
def infer_provider(klass, url):
|
def infer_provider( url):
|
||||||
if not url:
|
if not url:
|
||||||
return None
|
return None
|
||||||
# provider derived from url. returns provider value. remember to call save() afterward
|
# 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)
|
twitter_id = models.CharField(max_length=15, blank=True)
|
||||||
facebook_id = models.PositiveIntegerField(null=True)
|
facebook_id = models.PositiveIntegerField(null=True)
|
||||||
librarything_id = models.CharField(max_length=31, blank=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_id = models.CharField(max_length=32, null=True, blank=True)
|
||||||
goodreads_user_name = models.CharField(max_length=200, 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_auth_secret = models.TextField(null=True, blank=True)
|
||||||
goodreads_user_link = models.CharField(max_length=200, 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):
|
#class CampaignSurveyResponse(models.Model):
|
||||||
# # generic
|
# # generic
|
||||||
# campaign = models.ForeignKey("Campaign", related_name="surveyresponse", null=False)
|
# campaign = models.ForeignKey("Campaign", related_name="surveyresponse", null=False)
|
||||||
|
|
|
@ -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
|
# 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;
|
# transactions in different ways, resulting in different user-visible behavior;
|
||||||
# we need to set expectations appropriately
|
# we need to set expectations appropriately
|
||||||
|
# up_or_down is 'increased', 'decreased', or 'canceled'
|
||||||
if transaction==None or up_or_down==None:
|
if transaction==None or up_or_down==None:
|
||||||
return
|
return
|
||||||
notification.queue([transaction.user], "pledge_status_change", {
|
notification.queue([transaction.user], "pledge_status_change", {
|
||||||
|
|
|
@ -13,7 +13,7 @@ from decimal import Decimal as D
|
||||||
from selectable.forms import AutoCompleteSelectMultipleWidget,AutoCompleteSelectMultipleField
|
from selectable.forms import AutoCompleteSelectMultipleWidget,AutoCompleteSelectMultipleField
|
||||||
from selectable.forms import AutoCompleteSelectWidget,AutoCompleteSelectField
|
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.core.lookups import OwnerLookup
|
||||||
|
|
||||||
from regluit.utils.localdatetime import now
|
from regluit.utils.localdatetime import now
|
||||||
|
@ -187,6 +187,25 @@ class OpenCampaignForm(forms.ModelForm):
|
||||||
fields = 'name', 'work', 'managers'
|
fields = 'name', 'work', 'managers'
|
||||||
widgets = { 'work': forms.HiddenInput }
|
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):
|
class EditManagersForm(forms.ModelForm):
|
||||||
managers = AutoCompleteSelectMultipleField(
|
managers = AutoCompleteSelectMultipleField(
|
||||||
OwnerLookup,
|
OwnerLookup,
|
||||||
|
@ -285,58 +304,70 @@ class CampaignPledgeForm(forms.Form):
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
label="Pledge Amount",
|
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_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):
|
def clean_preapproval_amount(self):
|
||||||
data = self.cleaned_data['preapproval_amount']
|
preapproval_amount = self.cleaned_data['preapproval_amount']
|
||||||
if data is None:
|
if preapproval_amount is None:
|
||||||
raise forms.ValidationError(_("Please enter a pledge amount."))
|
raise forms.ValidationError(_("Please enter a pledge amount."))
|
||||||
return data
|
return preapproval_amount
|
||||||
|
|
||||||
# should we do validation on the premium_id here?
|
def clean_premium_id(self):
|
||||||
# can see whether it corresponds to a real premium -- do that here?
|
premium_id = self.cleaned_data['premium_id']
|
||||||
# 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
|
|
||||||
try:
|
try:
|
||||||
preapproval_amount = cleaned_data.get("preapproval_amount")
|
self.premium= Premium.objects.get(id=premium_id)
|
||||||
premium_id = int(cleaned_data.get("premium_id"))
|
if self.premium.limit>0:
|
||||||
premium_amount = Premium.objects.get(id=premium_id).amount
|
if self.premium.limit<=self.premium.premium_count:
|
||||||
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:
|
|
||||||
raise forms.ValidationError(_("Sorry, that premium is fully subscribed."))
|
raise forms.ValidationError(_("Sorry, that premium is fully subscribed."))
|
||||||
except Premium.DoesNotExist:
|
except Premium.DoesNotExist:
|
||||||
raise forms.ValidationError(_("Sorry, that premium is not valid."))
|
raise forms.ValidationError(_("Sorry, that premium is not valid."))
|
||||||
|
|
||||||
|
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:
|
except Exception, e:
|
||||||
if isinstance(e, forms.ValidationError):
|
if isinstance(e, forms.ValidationError):
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
return cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
class DonateForm(forms.Form):
|
class CCForm(forms.Form):
|
||||||
donation_amount = forms.DecimalField(
|
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,
|
required=False,
|
||||||
min_value=D('1.00'),
|
min_value=D('1.00'),
|
||||||
max_value=D('100000.00'),
|
max_value=D('100000.00'),
|
||||||
decimal_places=2,
|
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):
|
class GoodreadsShelfLoadingForm(forms.Form):
|
||||||
goodreads_shelf_name_number = forms.CharField(widget=forms.Select(choices=(
|
goodreads_shelf_name_number = forms.CharField(widget=forms.Select(choices=(
|
||||||
|
|
|
@ -2,22 +2,6 @@
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% url privacy as privacyurl %}
|
{% 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 %}
|
{% block content %}
|
||||||
|
|
||||||
|
@ -52,13 +36,5 @@
|
||||||
<p>No associated transactions</p>
|
<p>No associated transactions</p>
|
||||||
{% endif %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -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 %}
|
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -6,21 +6,28 @@
|
||||||
<li class="first parent">
|
<li class="first parent">
|
||||||
<span class="faq">How do I pledge?</span>
|
<span class="faq">How do I pledge?</span>
|
||||||
<span class="menu level2 answer">
|
<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>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="parent">
|
<li class="parent">
|
||||||
<span class="faq">Do I need an Amazon account to pledge?</span>
|
<span class="faq">Do I need an Amazon account to pledge?</span>
|
||||||
<span class="menu level2 answer">
|
<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>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="parent">
|
<li class="parent">
|
||||||
<span class="faq">When will I be charged?</span>
|
<span class="faq">When will I be charged?</span>
|
||||||
<span class="menu level2 answer">
|
<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>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "basedocumentation.html" %}
|
{% extends "basedocumentation.html" %}
|
||||||
{% block title %}Open Access eBooks{% endblock %}
|
{% block title %}Ungluers supporting the {{ campaign }}{% endblock %}
|
||||||
{% block extra_extra_head %}
|
{% block extra_extra_head %}
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/vanilla.css" class="day" title="day" />
|
<link rel="stylesheet" type="text/css" href="/static/css/vanilla.css" class="day" title="day" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -9,43 +9,45 @@
|
||||||
<div class="agate-info">
|
<div class="agate-info">
|
||||||
<p>© {{ campaign.work.preferred_edition.publication_date }} by {{ campaign.work.author }}</p>
|
<p>© {{ campaign.work.preferred_edition.publication_date }} by {{ campaign.work.author }}</p>
|
||||||
<p>ISBN: {{ campaign.work.preferred_edition.isbn_13 }} .</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><img src="images/unglueitlogo.png" alt="unglue.it logo" /></p>
|
||||||
<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.
|
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.
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section epub:type="other-credits" id="other-credits">
|
<section epub:type="other-credits" id="other-credits">
|
||||||
<h2>This is an unglued ebook</h2>
|
<h2>This is an unglued ebook</h2>
|
||||||
|
{% with campaign.ungluer_transactions as transactions %}
|
||||||
<p>
|
<p>
|
||||||
Unglued ebooks are made possible through the Unglue.it website by contributions from {{ campaign.ungluers.all|length }} readers like you.</p>
|
Unglued ebooks are made possible through the Unglue.it website by contributions from {{ transactions.all|length }} readers like you.</p>
|
||||||
{% if campaign.ungluers.supporters %}
|
{% if transactions.supporters %}
|
||||||
<p>Supporters of this edition include ungluers:
|
<p>Supporters of this edition:
|
||||||
{% for ungluer in campaign.ungluers.supporters %}
|
{% for transaction in transactions.supporters %}
|
||||||
{% if forloop.last %}and {% endif %}{{ ungluer.username }}{% if forloop.last %}. {% else %}, {% endif %}
|
{% if forloop.last %}{% if transactions.supporters|length > 1 %}and {% endif %}{% endif %}{{ transaction.ack_name }}{% if forloop.last %}. {% else %}, {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if campaign.ungluers.patrons %}
|
{% if transactions.patrons %}
|
||||||
<p>Benefactors of this edition include ungluers:
|
<p>Benefactors of this edition:
|
||||||
{% for ungluer in campaign.ungluers.patrons %}
|
{% for transaction in transactions.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 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 %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if campaign.ungluers.bibliophiles %}
|
{% if transactions.bibliophiles %}
|
||||||
<p>Bibliophiles of this edition include ungluers:<dl>
|
<p>Bibliophiles of this edition:<dl>
|
||||||
{% for ungluer in campaign.ungluers.bibliophiles %}
|
{% for transaction in transactions.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>
|
<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 %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<!-- trim here -->
|
<!-- trim here -->
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ Please fix the following before launching your campaign:
|
||||||
<div>
|
<div>
|
||||||
<div class="pubinfo">
|
<div class="pubinfo">
|
||||||
<h3 class="book-author">{{ work.author }}</h3>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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 & 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&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&url={{ nonprofit.link|urlencode }}&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 }}&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 © 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>
|
||||||
|
|
|
@ -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.
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -23,7 +23,7 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="pubinfo">
|
<div class="pubinfo">
|
||||||
<h3 class="book-author">{{ work.author }}</h3>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,8 +52,8 @@
|
||||||
{% if faqmenu == 'modify' %}
|
{% if faqmenu == 'modify' %}
|
||||||
<div class="modify_notification clearfix"><h4>You've already pledged to this campaign:</h4>
|
<div class="modify_notification clearfix"><h4>You've already pledged to this campaign:</h4>
|
||||||
<div>
|
<div>
|
||||||
Amount: ${{preapproval_amount|intcomma}}.<br />
|
Amount: ${{transaction.amount|intcomma}}.<br />
|
||||||
Your premium: {% if premium_description %}{{ premium_description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
|
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
|
||||||
</div>
|
</div>
|
||||||
<br /> You can modify your pledge below.
|
<br /> You can modify your pledge below.
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,11 +68,6 @@
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div class="pledge_amount">{{ form.preapproval_amount.label_tag }}: {{ form.preapproval_amount.errors }}${{ form.preapproval_amount }}</div>
|
<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>
|
<div class="pledge_amount premium_level">Choose your premium:</div>
|
||||||
|
|
||||||
<ul class="support menu" id="premiums_list">
|
<ul class="support menu" id="premiums_list">
|
||||||
|
@ -81,9 +76,15 @@
|
||||||
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
|
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
|
||||||
<label for="premium_{{premium_item.id}}">
|
<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 %} />
|
<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">
|
<span class="menu-item-price">
|
||||||
${{ premium_item.amount|intcomma }}
|
${{ premium_item.amount|intcomma }}
|
||||||
</span>
|
</span>
|
||||||
|
{% endif %}
|
||||||
<span class="menu-item-desc">
|
<span class="menu-item-desc">
|
||||||
{{ premium_item.description }} {% ifnotequal premium_item.limit 0 %}<br /> Only {{ premium_item.premium_remaining }} remaining! {% endifnotequal %}
|
{{ premium_item.description }} {% ifnotequal premium_item.limit 0 %}<br /> Only {{ premium_item.premium_remaining }} remaining! {% endifnotequal %}
|
||||||
</span>
|
</span>
|
||||||
|
@ -92,6 +93,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</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>. {{ 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). {{ 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="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" />
|
<input name="decoy" type="submit" id="fakepledgesubmit" disabled="disabled" />
|
||||||
{% comment %}
|
{% comment %}
|
||||||
|
@ -100,30 +108,17 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cancel_notice">(You will be sent to <b>{{ payment_processor|capfirst }}</b>
|
<div class="cancel_notice">
|
||||||
{% 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>
|
{% 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>
|
||||||
{% ifequal work.id 81724 %}
|
{% if transaction.ack_name %}
|
||||||
<!-- Google Code for OLA pledge page Conversion Page; allows for AdWords conversion tracking -->
|
<div id="pass_ack_name" style="display: none;">{{ transaction.ack_name }}</div>
|
||||||
<script type="text/javascript">
|
{% else %}
|
||||||
/* <![CDATA[ */
|
<div id="pass_ack_name" style="display: none;">{{ request.user.username }}</div>
|
||||||
var google_conversion_id = 1072527387;
|
{% endif %}
|
||||||
var google_conversion_language = "en";
|
<div id="pass_ack_link" style="display: none;">{{ transaction.ack_link }}</div>
|
||||||
var google_conversion_format = "3";
|
<div id="pass_ack_dedication" style="display: none;">{{ transaction.ack_dedication }}</div>
|
||||||
var google_conversion_color = "ffffff";
|
<div id="pass_anon" style="display: none;">{{ transaction.anonymous }}</div>
|
||||||
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&label=Vqw9CPi04AIQm_C1_wM&guid=ON&script=0"/>
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
{% endifequal %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
<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/searchandbrowse.css" />
|
||||||
<link type="text/css" rel="stylesheet" href="/static/css/book_panel.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/slides.min.jquery.js"></script>
|
||||||
<script src="/static/js/slideshow.js"></script>
|
<script src="/static/js/slideshow.js"></script>
|
||||||
|
@ -29,7 +31,14 @@
|
||||||
|
|
||||||
<h2 class="thank-you">Thank you!</h2>
|
<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>
|
<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"><iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"></iframe></textarea></div>
|
<div id="widgetcode">Copy/paste this into your site:<br /><textarea rows="7" cols="22"><iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"></iframe></textarea></div>
|
||||||
|
|
|
@ -130,7 +130,7 @@ $j(document).ready(function(){
|
||||||
<div>
|
<div>
|
||||||
<div class="pubinfo">
|
<div class="pubinfo">
|
||||||
<h3 class="book-author">{{ work.author }}</h3>
|
<h3 class="book-author">{{ work.author }}</h3>
|
||||||
<h3 class="book-year">{{ pubdate }}</h3>
|
<h3 class="book-year">{{ work.publication_date_year }}</h3>
|
||||||
</div>
|
</div>
|
||||||
{% if status == 'ACTIVE' %}
|
{% if status == 'ACTIVE' %}
|
||||||
{% if pledged %}
|
{% if pledged %}
|
||||||
|
|
|
@ -3,12 +3,14 @@ from django.views.generic.simple import direct_to_template
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from regluit.core.feeds import SupporterWishlistFeed
|
from regluit.core.feeds import SupporterWishlistFeed
|
||||||
from regluit.core.models import Campaign
|
from regluit.core.models import Campaign
|
||||||
from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeModifyView, PledgeCancelView, PledgeNeverMindView, PledgeRechargeView, FAQView
|
from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeCancelView, PledgeRechargeView, FAQView
|
||||||
from regluit.frontend.views import CampaignListView, DonateView, WorkListView, UngluedListView, InfoPageView, InfoLangView
|
from regluit.frontend.views import CampaignListView, WorkListView, UngluedListView, InfoPageView, InfoLangView, DonationView, FundPledgeView
|
||||||
|
from regluit.frontend.views import NonprofitCampaign, DonationCredit, PledgeModifiedView
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
"regluit.frontend.views",
|
"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>)(?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"^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"^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/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"),
|
||||||
url(r"^pledge/cancel/(?P<campaign_id>\d+)$", login_required(PledgeCancelView.as_view()), name="pledge_cancel"),
|
url(r"^pledge/cancel/(?P<campaign_id>\d+)$", login_required(PledgeCancelView.as_view()), name="pledge_cancel"),
|
||||||
url(r"^pledge/complete/$", login_required(PledgeCompleteView.as_view()), name="pledge_complete"),
|
url(r"^pledge/complete/$", login_required(PledgeCompleteView.as_view()), name="pledge_complete"),
|
||||||
url(r"^pledge/nevermind/$", login_required(PledgeNeverMindView.as_view()), name="pledge_nevermind"),
|
url(r"^pledge/modified/$", login_required(PledgeModifiedView.as_view()), name="pledge_modified"),
|
||||||
url(r"^pledge/modify/(?P<work_id>\d+)$", login_required(PledgeModifyView.as_view()), name="pledge_modify"),
|
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"^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"^subjects/$", "subjects", name="subjects"),
|
||||||
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),
|
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),
|
||||||
url(r"^librarything/load/$","librarything_load", name="librarything_load"),
|
url(r"^librarything/load/$","librarything_load", name="librarything_load"),
|
||||||
|
@ -85,6 +91,5 @@ if settings.DEBUG:
|
||||||
"regluit.frontend.views",
|
"regluit.frontend.views",
|
||||||
url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"),
|
url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"),
|
||||||
url(r"^goodreads/clear_wishlist/$","clear_wishlist", name="clear_wishlist"),
|
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"),
|
url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"),
|
||||||
)
|
)
|
|
@ -17,6 +17,7 @@ import oauth2 as oauth
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.core import signing
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.contrib import messages
|
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 RightsHolderForm, UserClaimForm, LibraryThingForm, OpenCampaignForm
|
||||||
from regluit.frontend.forms import getManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm, FeedbackForm
|
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 EbookForm, CustomPremiumForm, EditManagersForm, EditionForm, PledgeCancelForm
|
||||||
|
from regluit.frontend.forms import getTransferCreditForm, CCForm
|
||||||
from regluit.payment.manager import PaymentManager
|
from regluit.payment.manager import PaymentManager
|
||||||
from regluit.payment.models import Transaction
|
from regluit.payment.models import Transaction, Account
|
||||||
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, TRANSACTION_STATUS_NONE, TRANSACTION_STATUS_MODIFIED
|
||||||
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE
|
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION, PAYMENT_TYPE_INSTANT
|
||||||
from regluit.payment.paypal import Preapproval
|
from regluit.payment.parameters import PAYMENT_HOST_STRIPE
|
||||||
|
from regluit.payment.credit import credit_transaction
|
||||||
from regluit.core import goodreads
|
from regluit.core import goodreads
|
||||||
from tastypie.models import ApiKey
|
from tastypie.models import ApiKey
|
||||||
from regluit.payment.models import Transaction
|
from regluit.payment.models import Transaction, Sent, CreditLog
|
||||||
from notification import models as notification
|
from notification import models as notification
|
||||||
|
|
||||||
|
from regluit.payment import stripelib
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -154,6 +158,7 @@ def work(request, work_id, action='display'):
|
||||||
except:
|
except:
|
||||||
pledged = None
|
pledged = None
|
||||||
|
|
||||||
|
logger.info("pledged: {0}".format(pledged))
|
||||||
countdown = ""
|
countdown = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -185,11 +190,6 @@ def work(request, work_id, action='display'):
|
||||||
if action == 'preview':
|
if action == 'preview':
|
||||||
work.last_campaign_status = 'ACTIVE'
|
work.last_campaign_status = 'ACTIVE'
|
||||||
|
|
||||||
try:
|
|
||||||
pubdate = work.publication_date[:4]
|
|
||||||
except IndexError:
|
|
||||||
pubdate = 'unknown'
|
|
||||||
|
|
||||||
if not request.user.is_anonymous():
|
if not request.user.is_anonymous():
|
||||||
claimform = UserClaimForm( request.user, data={'claim-work':work.pk, 'claim-user': request.user.id}, prefix = 'claim')
|
claimform = UserClaimForm( request.user, data={'claim-work':work.pk, 'claim-user': request.user.id}, prefix = 'claim')
|
||||||
for edition in editions:
|
for edition in editions:
|
||||||
|
@ -210,8 +210,9 @@ def work(request, work_id, action='display'):
|
||||||
claimform = None
|
claimform = None
|
||||||
|
|
||||||
if campaign:
|
if campaign:
|
||||||
# pull up premiums explicitly tied to the campaign or generic premiums
|
# pull up premiums explicitly tied to the campaign
|
||||||
premiums = campaign.effective_premiums()
|
# mandatory premiums are only displayed in pledge process
|
||||||
|
premiums = campaign.custom_premiums()
|
||||||
else:
|
else:
|
||||||
premiums = None
|
premiums = None
|
||||||
|
|
||||||
|
@ -242,7 +243,6 @@ def work(request, work_id, action='display'):
|
||||||
'wishers': wishers,
|
'wishers': wishers,
|
||||||
'base_url': base_url,
|
'base_url': base_url,
|
||||||
'editions': editions,
|
'editions': editions,
|
||||||
'pubdate': pubdate,
|
|
||||||
'pledged': pledged,
|
'pledged': pledged,
|
||||||
'activetab': activetab,
|
'activetab': activetab,
|
||||||
'alert': alert,
|
'alert': alert,
|
||||||
|
@ -417,11 +417,6 @@ def manage_campaign(request, id):
|
||||||
|
|
||||||
work = campaign.work
|
work = campaign.work
|
||||||
|
|
||||||
try:
|
|
||||||
pubdate = work.publication_date[:4]
|
|
||||||
except IndexError:
|
|
||||||
pubdate = 'unknown'
|
|
||||||
|
|
||||||
return render(request, 'manage_campaign.html', {
|
return render(request, 'manage_campaign.html', {
|
||||||
'campaign': campaign,
|
'campaign': campaign,
|
||||||
'form':form,
|
'form':form,
|
||||||
|
@ -429,7 +424,6 @@ def manage_campaign(request, id):
|
||||||
'alerts': alerts,
|
'alerts': alerts,
|
||||||
'premiums' : campaign.effective_premiums(),
|
'premiums' : campaign.effective_premiums(),
|
||||||
'premium_form' : new_premium_form,
|
'premium_form' : new_premium_form,
|
||||||
'pubdate': pubdate,
|
|
||||||
'work': work,
|
'work': work,
|
||||||
'activetab': activetab,
|
'activetab': activetab,
|
||||||
})
|
})
|
||||||
|
@ -573,139 +567,145 @@ class CampaignListView(FilterableListView):
|
||||||
context['facet'] =self.kwargs['facet']
|
context['facet'] =self.kwargs['facet']
|
||||||
return context
|
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):
|
class PledgeView(FormView):
|
||||||
template_name="pledge.html"
|
template_name="pledge.html"
|
||||||
form_class = CampaignPledgeForm
|
form_class = CampaignPledgeForm
|
||||||
embedded = False
|
transaction = None
|
||||||
|
campaign = None
|
||||||
|
work = None
|
||||||
|
premiums = None
|
||||||
|
data = None
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get_preapproval_amount(self):
|
||||||
# change the default behavior from https://code.djangoproject.com/browser/django/tags/releases/1.3.1/django/views/generic/edit.py#L129
|
|
||||||
# don't automatically bind the data to the form on GET, only on POST
|
|
||||||
# compare with https://code.djangoproject.com/browser/django/tags/releases/1.3.1/django/views/generic/edit.py#L34
|
|
||||||
form_class = self.get_form_class()
|
|
||||||
form = form_class()
|
|
||||||
|
|
||||||
context_data = self.get_context_data(form=form)
|
|
||||||
# if there is already an active campaign pledge for user, redirect to the pledge modify page
|
|
||||||
if context_data.get('redirect_to_modify_pledge'):
|
|
||||||
work = context_data['work']
|
|
||||||
return HttpResponseRedirect(reverse('pledge_modify', args=[work.id]))
|
|
||||||
else:
|
|
||||||
return self.render_to_response(context_data)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
"""set up the pledge page"""
|
|
||||||
|
|
||||||
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
|
|
||||||
assert self.request.user.is_authenticated()
|
|
||||||
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)
|
|
||||||
preapproval_amount = self.request.REQUEST.get('preapproval_amount', None)
|
preapproval_amount = self.request.REQUEST.get('preapproval_amount', None)
|
||||||
|
if preapproval_amount:
|
||||||
if premium_id is not None and preapproval_amount is None:
|
return preapproval_amount
|
||||||
|
premium_id = self.request.REQUEST.get('premium_id', None)
|
||||||
|
if premium_id != None:
|
||||||
try:
|
try:
|
||||||
preapproval_amount = D(models.Premium.objects.get(id=premium_id).amount)
|
preapproval_amount = D(models.Premium.objects.get(id=premium_id).amount)
|
||||||
except:
|
except:
|
||||||
preapproval_amount = None
|
preapproval_amount = None
|
||||||
|
if self.transaction:
|
||||||
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id}
|
if preapproval_amount:
|
||||||
|
preapproval_amount = preapproval_amount if preapproval_amount>self.transaction.amount else self.transaction.amount
|
||||||
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)
|
|
||||||
else:
|
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:
|
try:
|
||||||
pubdate = work.publication_date[:4]
|
self.campaign = self.work.last_campaign()
|
||||||
except IndexError:
|
# TODO need to sort the premiums
|
||||||
pubdate = 'unknown'
|
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({
|
context.update({
|
||||||
'redirect_to_modify_pledge':False,
|
'work':self.work,
|
||||||
'work':work,'campaign':campaign,
|
'campaign':self.campaign,
|
||||||
'premiums':premiums, 'form':form,
|
'premiums':self.premiums,
|
||||||
'premium_id':premium_id,
|
'premium_id':self.data['premium_id'],
|
||||||
'faqmenu': 'pledge',
|
'faqmenu': 'modify' if self.transaction else 'pledge',
|
||||||
'pubdate':pubdate,
|
'transaction': self.transaction,
|
||||||
'payment_processor':settings.PAYMENT_PROCESSOR,
|
'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
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
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
|
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
|
||||||
campaign = models.Work.objects.get(id=int(work_id)).last_campaign()
|
|
||||||
|
|
||||||
premium_id = form.cleaned_data["premium_id"]
|
p = PaymentManager()
|
||||||
# confirm that the premium_id is a valid one for the campaign in question
|
if self.transaction:
|
||||||
try:
|
# modifying the transaction...
|
||||||
premium = models.Premium.objects.get(id=premium_id)
|
assert self.transaction.type == PAYMENT_TYPE_AUTHORIZATION and self.transaction.status == TRANSACTION_STATUS_ACTIVE
|
||||||
if not (premium.campaign is None or premium.campaign == campaign):
|
status, url = p.modify_transaction(self.transaction, form.cleaned_data["preapproval_amount"],
|
||||||
premium = None
|
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
|
||||||
except models.Premium.DoesNotExist, e:
|
pledge_extra=form.pledge_extra
|
||||||
premium = None
|
)
|
||||||
|
logger.info("status: {0}, url:{1}".format(status, url))
|
||||||
|
|
||||||
p = PaymentManager(embedded=self.embedded)
|
if status and url is not None:
|
||||||
|
logger.info("PledgeView (Modify): " + url)
|
||||||
# PledgeView is wrapped in login_required -- so in theory, user should never be None -- but I'll keep this logic here for now.
|
return HttpResponseRedirect(url)
|
||||||
if self.request.user.is_authenticated():
|
elif status and url is None:
|
||||||
user = self.request.user
|
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_modified'), self.transaction.id))
|
||||||
else:
|
else:
|
||||||
user = None
|
return HttpResponse("No modification made")
|
||||||
|
else:
|
||||||
if not self.embedded:
|
t, url = p.process_transaction('USD', form.cleaned_data["preapproval_amount"],
|
||||||
|
host = None,
|
||||||
return_url = None
|
campaign=self.campaign,
|
||||||
nevermind_url = None
|
user=self.request.user,
|
||||||
|
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
|
||||||
# the recipients of this authorization is not specified here but rather by the PaymentManager.
|
pledge_extra=form.pledge_extra
|
||||||
# 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)
|
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
logger.info("PledgeView url: " + url)
|
logger.info("PledgeView url: " + url)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
@ -713,133 +713,202 @@ class PledgeView(FormView):
|
||||||
logger.error("Attempt to produce transaction id {0} failed".format(t.id))
|
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.")
|
return HttpResponse("Our attempt to enable your transaction failed. We have logged this error.")
|
||||||
|
|
||||||
class PledgeModifyView(FormView):
|
class FundPledgeView(FormView):
|
||||||
"""
|
template_name="fund_the_pledge.html"
|
||||||
A view to handle request to change an existing pledge
|
form_class = CCForm
|
||||||
"""
|
transaction = None
|
||||||
|
|
||||||
template_name="pledge.html"
|
def get_form_kwargs(self):
|
||||||
form_class = CampaignPledgeForm
|
kwargs = super(FundPledgeView, self).get_form_kwargs()
|
||||||
embedded = False
|
|
||||||
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(FundPledgeView, self).get_context_data(**kwargs)
|
||||||
context = super(PledgeModifyView, self).get_context_data(**kwargs)
|
context['modified'] = self.transaction.status==TRANSACTION_STATUS_MODIFIED
|
||||||
|
context['preapproval_amount']=self.transaction.max_amount
|
||||||
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
|
context['needed'] = self.transaction.max_amount - self.request.user.credit.available
|
||||||
assert self.request.user.is_authenticated()
|
context['transaction']=self.transaction
|
||||||
user = self.request.user
|
context['nonprofit'] = settings.NONPROFIT
|
||||||
|
context['STRIPE_PK'] = stripelib.STRIPE_PK
|
||||||
work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
|
# note that get_form_kwargs() will already have been called once
|
||||||
|
donate_args=self.get_form_kwargs()
|
||||||
try:
|
donate_args['data']['preapproval_amount']=context['needed']
|
||||||
campaign = work.last_campaign()
|
context['donate_form'] = DonateForm(**donate_args)
|
||||||
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,
|
|
||||||
})
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
def form_invalid(self, form):
|
logger.info('request.POST: {0}'.format(request.POST))
|
||||||
logger.info("form.non_field_errors: {0}".format(form.non_field_errors()))
|
return super(FundPledgeView, self).post(request, *args, **kwargs)
|
||||||
response = self.render_to_response(self.get_context_data(form=form))
|
|
||||||
return response
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
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?
|
# first pass -- we have a token -- also do more direct coupling to stripelib -- then move to
|
||||||
# 2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
|
# abstraction of payment.manager / payment.baseprocessor
|
||||||
# if new amount is greater than max_amount...need to go out and get new approval.
|
|
||||||
# to start with, we can use the standard pledge_complete, pledge_cancel machinery
|
|
||||||
# might have to modify the pledge_complete, pledge_cancel because the messages are going to be
|
|
||||||
# different because we're modifying a pledge rather than a new one.
|
|
||||||
|
|
||||||
work_id = self.kwargs["work_id"]
|
# 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"]
|
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
|
user = self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
|
account.save()
|
||||||
campaign = models.Work.objects.get(id=int(work_id)).last_campaign()
|
|
||||||
assert campaign.status == 'ACTIVE'
|
|
||||||
|
|
||||||
premium_id = form.cleaned_data["premium_id"]
|
charge = sc.create_charge(preapproval_amount, customer=customer, description="${0} for test / retain cc".format(preapproval_amount))
|
||||||
# confirm that the premium_id is a valid one for the campaign in question
|
|
||||||
try:
|
|
||||||
premium = models.Premium.objects.get(id=premium_id)
|
|
||||||
if not (premium.campaign is None or premium.campaign == campaign):
|
|
||||||
premium = None
|
|
||||||
except models.Premium.DoesNotExist, e:
|
|
||||||
premium = None
|
|
||||||
|
|
||||||
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
|
|
||||||
assert transactions.count() == 1
|
|
||||||
transaction = transactions[0]
|
|
||||||
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
|
|
||||||
|
|
||||||
p = PaymentManager(embedded=self.embedded)
|
|
||||||
paymentReason = "Unglue.it Pledge for {0}".format(campaign.name)
|
|
||||||
status, url = p.modify_transaction(transaction=transaction, amount=preapproval_amount, premium=premium,
|
|
||||||
paymentReason=paymentReason)
|
|
||||||
|
|
||||||
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:
|
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):
|
class PledgeRechargeView(TemplateView):
|
||||||
|
@ -852,7 +921,7 @@ class PledgeRechargeView(TemplateView):
|
||||||
|
|
||||||
context = super(PledgeRechargeView, self).get_context_data(**kwargs)
|
context = super(PledgeRechargeView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
|
# the following should be true since PledgeView.as_view is wrapped in login_required
|
||||||
assert self.request.user.is_authenticated()
|
assert self.request.user.is_authenticated()
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
|
|
||||||
|
@ -871,15 +940,10 @@ class PledgeRechargeView(TemplateView):
|
||||||
|
|
||||||
if transaction is not None:
|
if transaction is not None:
|
||||||
# the recipients of this authorization is not specified here but rather by the PaymentManager.
|
# the recipients of this authorization is not specified here but rather by the PaymentManager.
|
||||||
# set the expiry date based on the campaign deadline
|
|
||||||
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
|
||||||
|
|
||||||
paymentReason = "Unglue.it Recharge for {0}".format(campaign.name)
|
paymentReason = "Unglue.it Recharge for {0}".format(campaign.name)
|
||||||
|
|
||||||
p = PaymentManager(embedded=False)
|
p = PaymentManager()
|
||||||
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, transaction.amount, expiry=expiry, campaign=campaign, list=None, user=user,
|
t, url = p.authorize(transaction, return_url=return_url, paymentReason=paymentReason)
|
||||||
return_url=return_url, nevermind_url=nevermind_url, anonymous=transaction.anonymous, premium=transaction.premium,
|
|
||||||
paymentReason=paymentReason)
|
|
||||||
logger.info("Recharge url: {0}".format(url))
|
logger.info("Recharge url: {0}".format(url))
|
||||||
else:
|
else:
|
||||||
url = None
|
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?
|
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
|
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 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)
|
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
|
# pick up all get and post parameters and display
|
||||||
context = super(PledgeCompleteView, self).get_context_data()
|
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():
|
if self.request.user.is_authenticated():
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
else:
|
else:
|
||||||
|
@ -938,12 +993,8 @@ class PledgeCompleteView(TemplateView):
|
||||||
work = None
|
work = None
|
||||||
|
|
||||||
# we need to check whether the user tied to the transaction is indeed the authenticated user.
|
# we need to check whether the user tied to the transaction is indeed the authenticated user.
|
||||||
|
|
||||||
correct_user = False
|
|
||||||
try:
|
try:
|
||||||
if user.id == transaction.user.id:
|
if user.id != transaction.user.id:
|
||||||
correct_user = True
|
|
||||||
else:
|
|
||||||
# should be 403 -- but let's try 404 for now -- 403 exception coming in Django 1.4
|
# should be 403 -- but let's try 404 for now -- 403 exception coming in Django 1.4
|
||||||
raise Http404
|
raise Http404
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
@ -961,7 +1012,7 @@ class PledgeCompleteView(TemplateView):
|
||||||
correct_transaction_type = False
|
correct_transaction_type = False
|
||||||
|
|
||||||
# add the work corresponding to the Transaction on the user's wishlist if it's not already on the wishlist
|
# 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?
|
# ok to overwrite Wishes.source?
|
||||||
user.wishlist.add_work(work, 'pledging')
|
user.wishlist.add_work(work, 'pledging')
|
||||||
|
|
||||||
|
@ -970,8 +1021,6 @@ class PledgeCompleteView(TemplateView):
|
||||||
works2 = worklist[4:8]
|
works2 = worklist[4:8]
|
||||||
|
|
||||||
context["transaction"] = transaction
|
context["transaction"] = transaction
|
||||||
context["correct_user"] = correct_user
|
|
||||||
context["correct_transaction_type"] = correct_transaction_type
|
|
||||||
context["work"] = work
|
context["work"] = work
|
||||||
context["campaign"] = campaign
|
context["campaign"] = campaign
|
||||||
context["faqmenu"] = "complete"
|
context["faqmenu"] = "complete"
|
||||||
|
@ -981,6 +1030,11 @@ class PledgeCompleteView(TemplateView):
|
||||||
|
|
||||||
return context
|
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):
|
class PledgeCancelView(FormView):
|
||||||
"""A view for allowing a user to cancel the active transaction for specified campaign"""
|
"""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))
|
logger.error("Exception from attempt to cancel pledge for campaign id {0} for username {1}: {2}".format(campaign_id, user.username, e))
|
||||||
return HttpResponse("Sorry, something went wrong in canceling your campaign pledge. We have logged this error.")
|
return HttpResponse("Sorry, something went wrong in canceling your campaign pledge. We have logged this error.")
|
||||||
|
|
||||||
|
|
||||||
class PledgeNeverMindView(TemplateView):
|
|
||||||
"""A callback for PayPal to tell unglue.it that a payment transaction has been canceled by the user"""
|
|
||||||
template_name="pledge_nevermind.html"
|
|
||||||
|
|
||||||
def get_context_data(self):
|
|
||||||
context = super(PledgeNeverMindView, self).get_context_data()
|
|
||||||
|
|
||||||
if self.request.user.is_authenticated():
|
|
||||||
user = self.request.user
|
|
||||||
else:
|
|
||||||
user = None
|
|
||||||
|
|
||||||
# pull out the transaction id and try to get the corresponding Transaction
|
|
||||||
transaction_id = self.request.REQUEST.get("tid")
|
|
||||||
transaction = Transaction.objects.get(id=transaction_id)
|
|
||||||
|
|
||||||
# work and campaign in question
|
|
||||||
try:
|
|
||||||
campaign = transaction.campaign
|
|
||||||
work = campaign.work
|
|
||||||
except Exception, e:
|
|
||||||
campaign = None
|
|
||||||
work = None
|
|
||||||
|
|
||||||
# we need to check whether the user tied to the transaction is indeed the authenticated user.
|
|
||||||
|
|
||||||
correct_user = False
|
|
||||||
try:
|
|
||||||
if user.id == transaction.user.id:
|
|
||||||
correct_user = True
|
|
||||||
except Exception, e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# check that the user had not already approved the transaction
|
|
||||||
# do we need to first run PreapprovalDetails to check on the status
|
|
||||||
|
|
||||||
# is it of type=PAYMENT_TYPE_AUTHORIZATION and status is NONE or ACTIVE (but approved is false)
|
|
||||||
|
|
||||||
if transaction.type == PAYMENT_TYPE_AUTHORIZATION:
|
|
||||||
correct_transaction_type = True
|
|
||||||
else:
|
|
||||||
correct_transaction_type = False
|
|
||||||
|
|
||||||
# status?
|
|
||||||
|
|
||||||
# give the user an opportunity to approved the transaction again
|
|
||||||
# provide a URL to click on.
|
|
||||||
# https://www.sandbox.paypal.com/?cmd=_ap-preapproval&preapprovalkey=PA-6JV656290V840615H
|
|
||||||
try_again_url = '%s?cmd=_ap-preapproval&preapprovalkey=%s' % (settings.PAYPAL_PAYMENT_HOST, transaction.preapproval_key)
|
|
||||||
|
|
||||||
context["transaction"] = transaction
|
|
||||||
context["correct_user"] = correct_user
|
|
||||||
context["correct_transaction_type"] = correct_transaction_type
|
|
||||||
context["try_again_url"] = try_again_url
|
|
||||||
context["work"] = work
|
|
||||||
context["campaign"] = campaign
|
|
||||||
context["faqmenu"] = "cancel"
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class DonateView(FormView):
|
|
||||||
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):
|
def claim(request):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
data = request.GET
|
data = request.GET
|
||||||
|
@ -2102,3 +2049,4 @@ def download(request, work_id):
|
||||||
def about(request, facet):
|
def about(request, facet):
|
||||||
template = "about_" + facet + ".html"
|
template = "about_" + facet + ".html"
|
||||||
return render(request, template)
|
return render(request, template)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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())
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -4,15 +4,11 @@ from django.contrib.auth.models import User
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from regluit.payment.parameters import *
|
from regluit.payment.parameters import *
|
||||||
from regluit.payment.paypal import IPN_SENDER_STATUS_COMPLETED
|
|
||||||
from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created
|
from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created
|
||||||
|
from regluit.payment import credit
|
||||||
|
|
||||||
if settings.PAYMENT_PROCESSOR == 'paypal':
|
from regluit.payment.baseprocessor import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
|
||||||
from regluit.payment.paypal import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
|
|
||||||
from regluit.payment.paypal import Pay as Execute
|
|
||||||
|
|
||||||
elif settings.PAYMENT_PROCESSOR == 'amazon':
|
|
||||||
from regluit.payment.amazon import Pay, Execute, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
|
|
||||||
import uuid
|
import uuid
|
||||||
import traceback
|
import traceback
|
||||||
from regluit.utils.localdatetime import now
|
from regluit.utils.localdatetime import now
|
||||||
|
@ -21,6 +17,8 @@ import logging
|
||||||
from decimal import Decimal as D
|
from decimal import Decimal as D
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
import urllib, urlparse
|
import urllib, urlparse
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -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
|
# at this point, there is no internal context and therefore, the methods of PaymentManager can be recast into static methods
|
||||||
class PaymentManager( object ):
|
class PaymentManager( object ):
|
||||||
|
|
||||||
def __init__( self, embedded=False):
|
|
||||||
self.embedded = embedded
|
|
||||||
|
|
||||||
def processIPN(self, request, module):
|
def processIPN(self, request, module):
|
||||||
|
|
||||||
# Forward to our payment processor
|
# 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())
|
logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def authorize(self, currency, target, amount, expiry=None, campaign=None, list=None, user=None,
|
def authorize(self, transaction, expiry= None, return_url=None, paymentReason="unglue.it Pledge", modification=False):
|
||||||
return_url=None, nevermind_url=None, anonymous=False, premium=None,
|
|
||||||
paymentReason="unglue.it Pledge", modification=False):
|
|
||||||
'''
|
'''
|
||||||
authorize
|
authorize
|
||||||
|
|
||||||
authorizes a set amount of money to be collected at a later date
|
authorizes a set amount of money to be collected at a later date
|
||||||
|
|
||||||
currency: a 3-letter paypal currency code, i.e. USD
|
|
||||||
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
|
|
||||||
amount: the amount to authorize
|
|
||||||
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
|
|
||||||
list: optional list object(to be set with TARGET_TYPE_LIST)
|
|
||||||
user: optional user object
|
|
||||||
return_url: url to redirect supporter to after a successful PayPal transaction
|
return_url: url to redirect supporter to after a successful PayPal transaction
|
||||||
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
|
|
||||||
anonymous: whether this pledge is anonymous
|
|
||||||
premium: the premium selected by the supporter for this transaction
|
|
||||||
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
||||||
modification: whether this authorize call is part of a modification of an existing pledge
|
modification: whether this authorize call is part of a modification of an existing pledge
|
||||||
|
|
||||||
|
@ -567,27 +551,13 @@ class PaymentManager( object ):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
t = Transaction.objects.create(amount=amount,
|
if host==None:
|
||||||
max_amount=amount,
|
#TODO send user to select a payment processor
|
||||||
type=PAYMENT_TYPE_AUTHORIZATION,
|
pass
|
||||||
execution = EXECUTE_TYPE_CHAINED_DELAYED,
|
|
||||||
target=target,
|
|
||||||
currency=currency,
|
|
||||||
status='NONE',
|
|
||||||
campaign=campaign,
|
|
||||||
list=list,
|
|
||||||
user=user,
|
|
||||||
anonymous=anonymous,
|
|
||||||
premium=premium
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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.
|
# here because we have immediate access to the Transaction object.
|
||||||
|
|
||||||
if nevermind_url is None:
|
|
||||||
nevermind_path = "{0}?{1}".format(reverse('pledge_nevermind'),
|
|
||||||
urllib.urlencode({'tid':t.id}))
|
|
||||||
nevermind_url = urlparse.urljoin(settings.BASE_URL, nevermind_path)
|
|
||||||
|
|
||||||
if return_url is None:
|
if return_url is None:
|
||||||
return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
||||||
|
@ -595,7 +565,7 @@ class PaymentManager( object ):
|
||||||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||||
|
|
||||||
method = getattr(t.get_payment_class(), "Preapproval")
|
method = getattr(t.get_payment_class(), "Preapproval")
|
||||||
p = method(t, amount, expiry, return_url=return_url, nevermind_url=nevermind_url, paymentReason=paymentReason)
|
p = method(transaction, transaction.max_amount, expiry, return_url=return_url, paymentReason=paymentReason)
|
||||||
|
|
||||||
# Create a response for this
|
# Create a response for this
|
||||||
envelope = p.envelope()
|
envelope = p.envelope()
|
||||||
|
@ -605,11 +575,11 @@ class PaymentManager( object ):
|
||||||
correlation_id = p.correlation_id(),
|
correlation_id = p.correlation_id(),
|
||||||
timestamp = p.timestamp(),
|
timestamp = p.timestamp(),
|
||||||
info = p.raw_response,
|
info = p.raw_response,
|
||||||
transaction=t)
|
transaction=transaction)
|
||||||
|
|
||||||
if p.success() and not p.error():
|
if p.success() and not p.error():
|
||||||
t.preapproval_key = p.key()
|
transaction.preapproval_key = p.key()
|
||||||
t.save()
|
transaction.save()
|
||||||
|
|
||||||
url = p.next_url()
|
url = p.next_url()
|
||||||
|
|
||||||
|
@ -629,16 +599,62 @@ class PaymentManager( object ):
|
||||||
# send the notice here for now
|
# send the notice here for now
|
||||||
# this is actually premature since we're only about to send the user off to the payment system to
|
# this is actually premature since we're only about to send the user off to the payment system to
|
||||||
# authorize a charge
|
# authorize a charge
|
||||||
pledge_created.send(sender=self, transaction=t)
|
pledge_created.send(sender=self, transaction=transaction)
|
||||||
|
|
||||||
return t, url
|
return transaction, url
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
t.error = p.error_string()
|
transaction.error = p.error_string()
|
||||||
t.save()
|
transaction.save()
|
||||||
logger.info("Authorize Error: " + p.error_string())
|
logger.info("Authorize Error: " + p.error_string())
|
||||||
return 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):
|
def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None):
|
||||||
'''
|
'''
|
||||||
|
@ -682,26 +698,26 @@ class PaymentManager( object ):
|
||||||
|
|
||||||
return canceled
|
return canceled
|
||||||
|
|
||||||
def modify_transaction(self, transaction, amount, expiry=None, anonymous=None, premium=None,
|
def modify_transaction(self, transaction, amount, expiry=None, pledge_extra=None,
|
||||||
return_url=None, nevermind_url=None,
|
return_url=None, nevermind_url=None, paymentReason=None):
|
||||||
paymentReason=None):
|
|
||||||
'''
|
'''
|
||||||
modify
|
modify
|
||||||
|
|
||||||
Modifies a transaction. The only type of modification allowed is to the amount and expiration date
|
Modifies a transaction.
|
||||||
|
2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
|
||||||
|
if new amount is greater than max_amount...need to go out and get new approval.
|
||||||
|
to start with, we can use the standard pledge_complete, pledge_cancel machinery
|
||||||
|
might have to modify the pledge_complete, pledge_cancel because the messages are going to be
|
||||||
|
different because we're modifying a pledge rather than a new one.
|
||||||
|
|
||||||
amount: the new amount
|
amount: the new amount
|
||||||
expiry: the new expiration date, or if none the current expiration date will be used
|
expiry: the new expiration date, or if none the current expiration date will be used
|
||||||
anonymous: new anonymous value; if None, then keep old value
|
|
||||||
premium: new premium selected; if None, then keep old value
|
|
||||||
return_url: the return URL after the preapproval(if needed)
|
return_url: the return URL after the preapproval(if needed)
|
||||||
nevermind_url: the cancel url after the preapproval(if needed)
|
|
||||||
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
||||||
|
|
||||||
return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed
|
return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Can only modify the amount of a preapproval for now
|
|
||||||
if transaction.type != PAYMENT_TYPE_AUTHORIZATION:
|
if transaction.type != PAYMENT_TYPE_AUTHORIZATION:
|
||||||
logger.info("Error, attempt to modify an invalid transaction type")
|
logger.info("Error, attempt to modify an invalid transaction type")
|
||||||
return False, None
|
return False, None
|
||||||
|
@ -712,31 +728,57 @@ class PaymentManager( object ):
|
||||||
logger.info("Error, attempt to modify a transaction that is not active")
|
logger.info("Error, attempt to modify a transaction that is not active")
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
# if any of expiry, anonymous, or premium is None, use the existing value
|
if transaction.host == PAYMENT_HOST_CREDIT:
|
||||||
if expiry is None:
|
# does user have enough credit to pledge now?
|
||||||
expiry = transaction.date_expired
|
if transaction.user.credit.available >= amount-transaction.amount :
|
||||||
if anonymous is None:
|
# YES!
|
||||||
anonymous = transaction.anonymous
|
transaction.set_pledge_extra(pledge_extra)
|
||||||
if premium is None:
|
credit.pledge_transaction(transaction,transaction.user,amount)
|
||||||
premium = transaction.premium
|
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,
|
# set the expiry date based on the campaign deadline
|
||||||
transaction.target,
|
expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
||||||
amount,
|
|
||||||
expiry,
|
# Start a new transaction for the new amount
|
||||||
transaction.campaign,
|
t = Transaction.create(amount=amount,
|
||||||
transaction.list,
|
max_amount=amount,
|
||||||
transaction.user,
|
host=transaction.host,
|
||||||
return_url,
|
currency=transaction.currency,
|
||||||
nevermind_url,
|
status=TRANSACTION_STATUS_CREATED,
|
||||||
transaction.anonymous,
|
campaign=transaction.campaign,
|
||||||
premium,
|
user=transaction.user,
|
||||||
paymentReason,
|
pledge_extra=pledge_extra
|
||||||
True)
|
)
|
||||||
|
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:
|
if t and url:
|
||||||
# Need to re-direct to approve the transaction
|
# Need to re-direct to approve the transaction
|
||||||
|
@ -761,8 +803,7 @@ class PaymentManager( object ):
|
||||||
elif amount <= transaction.max_amount:
|
elif amount <= transaction.max_amount:
|
||||||
# Update transaction but leave the preapproval alone
|
# Update transaction but leave the preapproval alone
|
||||||
transaction.amount = amount
|
transaction.amount = amount
|
||||||
transaction.anonymous = anonymous
|
transaction.set_pledge_extra(pledge_extra)
|
||||||
transaction.premium = premium
|
|
||||||
|
|
||||||
transaction.save()
|
transaction.save()
|
||||||
logger.info("Updated amount of transaction to %f" % amount)
|
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())
|
logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None, 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
|
pledge
|
||||||
|
|
||||||
Performs an instant payment
|
Performs an instant payment
|
||||||
|
|
||||||
currency: a 3-letter paypal currency code, i.e. USD
|
currency: a 3-letter paypal currency code, i.e. USD
|
||||||
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
|
campaign: required campaign object
|
||||||
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)
|
|
||||||
user: optional user object
|
user: optional user object
|
||||||
return_url: url to redirect supporter to after a successful PayPal transaction
|
return_url: url to redirect supporter to after a successful PayPal transaction
|
||||||
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
|
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
|
||||||
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,
|
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
|
||||||
the redirect url will be None
|
the redirect url will be None
|
||||||
|
@ -849,25 +880,18 @@ class PaymentManager( object ):
|
||||||
|
|
||||||
amount = D('0.00')
|
amount = D('0.00')
|
||||||
|
|
||||||
# for chained payments, first amount is the total amount
|
t = Transaction.create(amount=amount,
|
||||||
amount = D(receiver_list[0]['amount'])
|
|
||||||
|
|
||||||
t = Transaction.objects.create(amount=amount,
|
|
||||||
max_amount=amount,
|
max_amount=amount,
|
||||||
type=PAYMENT_TYPE_INSTANT,
|
|
||||||
execution=EXECUTE_TYPE_CHAINED_INSTANT,
|
|
||||||
target=target,
|
|
||||||
currency=currency,
|
currency=currency,
|
||||||
status='NONE',
|
status=TRANSACTION_STATUS_NONE,
|
||||||
campaign=campaign,
|
campaign=campaign,
|
||||||
list=list,
|
|
||||||
user=user,
|
user=user,
|
||||||
date_payment=now(),
|
pledge_extra=pledge_extra
|
||||||
anonymous=anonymous,
|
|
||||||
premium=premium
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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")
|
method = getattr(t.get_payment_class(), "Pay")
|
||||||
p = method(t,return_url=return_url, nevermind_url=nevermind_url)
|
p = method(t,return_url=return_url, nevermind_url=nevermind_url)
|
||||||
|
|
||||||
|
@ -887,10 +911,6 @@ class PaymentManager( object ):
|
||||||
t.status = TRANSACTION_STATUS_CREATED
|
t.status = TRANSACTION_STATUS_CREATED
|
||||||
t.save()
|
t.save()
|
||||||
|
|
||||||
if self.embedded:
|
|
||||||
url = p.embedded_url()
|
|
||||||
logger.info(url)
|
|
||||||
else:
|
|
||||||
url = p.next_url()
|
url = p.next_url()
|
||||||
|
|
||||||
logger.info("Pledge Success: " + url)
|
logger.info("Pledge Success: " + url)
|
||||||
|
|
|
@ -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']
|
|
@ -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']
|
|
@ -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']
|
|
@ -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']
|
|
@ -1,11 +1,23 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.conf import settings
|
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.parameters import *
|
||||||
|
from regluit.payment.signals import credit_balance_added, pledge_created
|
||||||
|
from regluit.utils.localdatetime import now
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from datetime import timedelta
|
||||||
import uuid
|
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):
|
class Transaction(models.Model):
|
||||||
|
|
||||||
|
@ -13,16 +25,13 @@ class Transaction(models.Model):
|
||||||
type = models.IntegerField(default=PAYMENT_TYPE_NONE, null=False)
|
type = models.IntegerField(default=PAYMENT_TYPE_NONE, null=False)
|
||||||
|
|
||||||
# host: the payment processor. Named after the payment module that hosts the payment processing functions
|
# host: the payment processor. Named after the payment module that hosts the payment processing functions
|
||||||
host = models.CharField(default=settings.PAYMENT_PROCESSOR, max_length=32, null=False)
|
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
|
||||||
|
|
||||||
# target: e.g, TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST -- defined in parameters.py
|
|
||||||
target = models.IntegerField(default=TARGET_TYPE_NONE, null=False)
|
|
||||||
|
|
||||||
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
|
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
|
||||||
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
|
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
|
||||||
|
|
||||||
# status: general status constants defined in parameters.py
|
# status: general status constants defined in parameters.py
|
||||||
status = models.CharField(max_length=32, default='None', null=False)
|
status = models.CharField(max_length=32, default=TRANSACTION_STATUS_NONE, null=False)
|
||||||
|
|
||||||
# local_status: status code specific to the payment processor
|
# local_status: status code specific to the payment processor
|
||||||
local_status = models.CharField(max_length=32, default='NONE', null=True)
|
local_status = models.CharField(max_length=32, default='NONE', null=True)
|
||||||
|
@ -72,12 +81,17 @@ class Transaction(models.Model):
|
||||||
campaign = models.ForeignKey(Campaign, null=True)
|
campaign = models.ForeignKey(Campaign, null=True)
|
||||||
premium = models.ForeignKey(Premium, null=True)
|
premium = models.ForeignKey(Premium, null=True)
|
||||||
|
|
||||||
# list: makes allowance for pledging against a Wishlist: not currently in use
|
# how to acknowledge the user on the supporter page of the campaign ebook
|
||||||
list = models.ForeignKey(Wishlist, null=True)
|
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
|
# whether the user wants to be not listed publicly
|
||||||
anonymous = models.BooleanField(null=False)
|
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):
|
def save(self, *args, **kwargs):
|
||||||
if not self.secret:
|
if not self.secret:
|
||||||
self.secret = str(uuid.uuid1())
|
self.secret = str(uuid.uuid1())
|
||||||
|
@ -103,6 +117,50 @@ class Transaction(models.Model):
|
||||||
mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)])
|
mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)])
|
||||||
return mod
|
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):
|
class PaymentResponse(models.Model):
|
||||||
# The API used
|
# The API used
|
||||||
|
@ -143,6 +201,120 @@ class Receiver(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"Receiver -- email: {0} status: {1} transaction: {2}".format(self.email, self.status, unicode(self.transaction))
|
return u"Receiver -- email: {0} status: {1} transaction: {2}".format(self.email, self.status, unicode(self.transaction))
|
||||||
|
|
||||||
|
class CreditLog(models.Model):
|
||||||
|
# a write only record of Donation Credit Transactions
|
||||||
|
user = models.ForeignKey(User, null=True)
|
||||||
|
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||||
|
timestamp = models.DateTimeField(auto_now=True)
|
||||||
|
action = models.CharField(max_length=16)
|
||||||
|
# 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
|
from django.db.models.signals import post_save, post_delete
|
||||||
import regluit.payment.manager
|
import regluit.payment.manager
|
||||||
|
|
||||||
|
|
|
@ -5,29 +5,30 @@ PAYMENT_TYPE_AUTHORIZATION = 2
|
||||||
PAYMENT_HOST_NONE = "none"
|
PAYMENT_HOST_NONE = "none"
|
||||||
PAYMENT_HOST_PAYPAL = "paypal"
|
PAYMENT_HOST_PAYPAL = "paypal"
|
||||||
PAYMENT_HOST_AMAZON = "amazon"
|
PAYMENT_HOST_AMAZON = "amazon"
|
||||||
|
PAYMENT_HOST_STRIPE = "stripe"
|
||||||
|
|
||||||
|
PAYMENT_HOST_TEST = "test"
|
||||||
|
PAYMENT_HOST_CREDIT = "credit"
|
||||||
|
|
||||||
EXECUTE_TYPE_NONE = 0
|
EXECUTE_TYPE_NONE = 0
|
||||||
EXECUTE_TYPE_CHAINED_INSTANT = 1
|
EXECUTE_TYPE_CHAINED_INSTANT = 1
|
||||||
EXECUTE_TYPE_CHAINED_DELAYED = 2
|
EXECUTE_TYPE_CHAINED_DELAYED = 2
|
||||||
EXECUTE_TYPE_PARALLEL = 3
|
EXECUTE_TYPE_PARALLEL = 3
|
||||||
|
|
||||||
TARGET_TYPE_NONE = 0
|
|
||||||
TARGET_TYPE_CAMPAIGN = 1
|
|
||||||
TARGET_TYPE_LIST = 2
|
|
||||||
TARGET_TYPE_DONATION = 3
|
|
||||||
|
|
||||||
# The default status for a transaction that is newly created
|
# The default status for a transaction that is newly created
|
||||||
TRANSACTION_STATUS_NONE = 'None'
|
TRANSACTION_STATUS_NONE = 'None'
|
||||||
|
|
||||||
# Indicates a transaction has been sent to the co-branded API
|
# Indicates a transaction has been sent to the co-branded API
|
||||||
TRANSACTION_STATUS_CREATED = 'Created'
|
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'
|
TRANSACTION_STATUS_COMPLETE = 'Complete'
|
||||||
|
|
||||||
# A general pending code that means in process
|
# A general pending code that means in process
|
||||||
TRANSACTION_STATUS_PENDING = 'Pending'
|
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
|
# Indicates a preapproval is active
|
||||||
TRANSACTION_STATUS_ACTIVE = 'Active'
|
TRANSACTION_STATUS_ACTIVE = 'Active'
|
||||||
|
@ -47,6 +48,3 @@ TRANSACTION_STATUS_REFUNDED = 'Refunded'
|
||||||
# The transaction was refused/denied
|
# The transaction was refused/denied
|
||||||
TRANSACTION_STATUS_FAILED = 'Failed'
|
TRANSACTION_STATUS_FAILED = 'Failed'
|
||||||
|
|
||||||
# these two following parameters are probably extraneous since I think we will compute dynamically where to return each time.
|
|
||||||
COMPLETE_URL = '/paymentcomplete'
|
|
||||||
NEVERMIND_URL = '/paymentnevermind'
|
|
||||||
|
|
1023
payment/paypal.py
1023
payment/paypal.py
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,38 @@
|
||||||
|
from notification import models as notification
|
||||||
from django.dispatch import Signal
|
from django.dispatch import Signal
|
||||||
|
|
||||||
transaction_charged = Signal(providing_args=["transaction"])
|
transaction_charged = Signal(providing_args=["transaction"])
|
||||||
pledge_created = Signal(providing_args=["transaction"])
|
pledge_created = Signal(providing_args=["transaction"])
|
||||||
pledge_modified = Signal(providing_args=["transaction", "up_or_down"])
|
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)
|
|
@ -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)
|
|
@ -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 %}
|
||||||
|
|
|
@ -190,7 +190,7 @@ class PledgeTest(TestCase):
|
||||||
|
|
||||||
# Note, set this to 1-5 different receivers with absolute amounts for each
|
# Note, set this to 1-5 different receivers with absolute amounts for each
|
||||||
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00}]
|
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00}]
|
||||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None)
|
t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
|
||||||
|
|
||||||
self.validateRedirect(t, url, 1)
|
self.validateRedirect(t, url, 1)
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ class PledgeTest(TestCase):
|
||||||
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00},
|
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00},
|
||||||
{'email':settings.PAYPAL_TEST_RH_EMAIL, 'amount':10.00}]
|
{'email':settings.PAYPAL_TEST_RH_EMAIL, 'amount':10.00}]
|
||||||
|
|
||||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None)
|
t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
|
||||||
|
|
||||||
self.validateRedirect(t, url, 2)
|
self.validateRedirect(t, url, 2)
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ class PledgeTest(TestCase):
|
||||||
|
|
||||||
# Note, set this to 1-5 different receivers with absolute amounts for each
|
# Note, set this to 1-5 different receivers with absolute amounts for each
|
||||||
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':50000.00}]
|
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':50000.00}]
|
||||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None)
|
t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
|
||||||
|
|
||||||
self.validateRedirect(t, url, 1)
|
self.validateRedirect(t, url, 1)
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ class AuthorizeTest(TestCase):
|
||||||
|
|
||||||
# Note, set this to 1-5 different receivers with absolute amounts for each
|
# Note, set this to 1-5 different receivers with absolute amounts for each
|
||||||
|
|
||||||
t, url = p.authorize('USD', TARGET_TYPE_NONE, 100.0, campaign=None, list=None, user=None)
|
t, url = p.authorize(t)
|
||||||
|
|
||||||
self.validateRedirect(t, url)
|
self.validateRedirect(t, url)
|
||||||
|
|
||||||
|
@ -301,6 +301,31 @@ class AuthorizeTest(TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.selenium.quit()
|
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):
|
class TransactionTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""
|
"""
|
||||||
|
@ -363,7 +388,7 @@ class BasicGuiTest(TestCase):
|
||||||
def suite():
|
def suite():
|
||||||
|
|
||||||
#testcases = [PledgeTest, AuthorizeTest, TransactionTest]
|
#testcases = [PledgeTest, AuthorizeTest, TransactionTest]
|
||||||
testcases = [TransactionTest]
|
testcases = [TransactionTest, CreditTest]
|
||||||
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
||||||
return suites
|
return suites
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
from django.conf.urls.defaults import *
|
from django.conf.urls.defaults import *
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from regluit.payment.views import StripeView
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
"regluit.payment.views",
|
"regluit.payment.views",
|
||||||
url(r"^handleipn/(?P<module>\w+)$", "handleIPN", name="HandleIPN"),
|
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
|
# this should be on only if DEBUG is on
|
||||||
|
|
||||||
|
@ -22,12 +18,11 @@ if settings.DEBUG:
|
||||||
url(r"^testexecute", "testExecute"),
|
url(r"^testexecute", "testExecute"),
|
||||||
url(r"^testcancel", "testCancel"),
|
url(r"^testcancel", "testCancel"),
|
||||||
url(r"^querycampaign", "queryCampaign"),
|
url(r"^querycampaign", "queryCampaign"),
|
||||||
url(r"^runtests", "runTests"),
|
|
||||||
url(r"^paymentcomplete","paymentcomplete"),
|
|
||||||
url(r"^checkstatus", "checkStatus"),
|
url(r"^checkstatus", "checkStatus"),
|
||||||
url(r"^testfinish", "testFinish"),
|
url(r"^testfinish", "testFinish"),
|
||||||
url(r"^testrefund", "testRefund"),
|
url(r"^testrefund", "testRefund"),
|
||||||
url(r"^testmodify", "testModify"),
|
url(r"^testmodify", "testModify"),
|
||||||
|
url(r"^stripe/test", StripeView.as_view())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
from regluit.payment.manager import PaymentManager
|
from regluit.payment.manager import PaymentManager
|
||||||
from regluit.payment.paypal import IPN
|
|
||||||
from regluit.payment.models import Transaction
|
from regluit.payment.models import Transaction
|
||||||
from regluit.core.models import Campaign, Wishlist
|
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.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.shortcuts import render_to_response
|
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.test.utils import setup_test_environment
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
|
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
from unittest import TestResult
|
from unittest import TestResult
|
||||||
from regluit.payment.tests import PledgeTest, AuthorizeTest
|
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from decimal import Decimal as D
|
from decimal import Decimal as D
|
||||||
|
|
||||||
|
@ -113,12 +121,8 @@ def testAuthorize(request):
|
||||||
receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00},
|
receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00},
|
||||||
{'email': TEST_RECEIVERS[1], 'amount':10.00}]
|
{'email': TEST_RECEIVERS[1], 'amount':10.00}]
|
||||||
|
|
||||||
if campaign_id:
|
|
||||||
campaign = Campaign.objects.get(id=int(campaign_id))
|
campaign = Campaign.objects.get(id=int(campaign_id))
|
||||||
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, amount, campaign=campaign, 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)
|
||||||
|
|
||||||
else:
|
|
||||||
t, url = p.authorize('USD', TARGET_TYPE_NONE, amount, campaign=None, return_url=None, list=None, user=None)
|
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
logger.info("testAuthorize: " + url)
|
logger.info("testAuthorize: " + url)
|
||||||
|
@ -249,12 +253,9 @@ def testPledge(request):
|
||||||
else:
|
else:
|
||||||
receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}]
|
receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}]
|
||||||
|
|
||||||
if campaign_id:
|
|
||||||
campaign = Campaign.objects.get(id=int(campaign_id))
|
campaign = Campaign.objects.get(id=int(campaign_id))
|
||||||
t, url = p.pledge('USD', 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:
|
if url:
|
||||||
logger.info("testPledge: " + url)
|
logger.info("testPledge: " + url)
|
||||||
|
@ -265,33 +266,6 @@ def testPledge(request):
|
||||||
logger.info("testPledge: Error " + str(t.error))
|
logger.info("testPledge: Error " + str(t.error))
|
||||||
return HttpResponse(response)
|
return HttpResponse(response)
|
||||||
|
|
||||||
def runTests(request):
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Setup the test environement. We need to run these tests on a live server
|
|
||||||
# so our code can receive IPN notifications from paypal
|
|
||||||
setup_test_environment()
|
|
||||||
result = TestResult()
|
|
||||||
|
|
||||||
# Run the authorize test
|
|
||||||
test = AuthorizeTest('test_authorize')
|
|
||||||
test.run(result)
|
|
||||||
|
|
||||||
# Run the pledge test
|
|
||||||
test = PledgeTest('test_pledge_single_receiver')
|
|
||||||
test.run(result)
|
|
||||||
|
|
||||||
# Run the pledge failure test
|
|
||||||
test = PledgeTest('test_pledge_too_much')
|
|
||||||
test.run(result)
|
|
||||||
|
|
||||||
output = "Tests Run: " + str(result.testsRun) + str(result.errors) + str(result.failures)
|
|
||||||
logger.info(output)
|
|
||||||
|
|
||||||
return HttpResponse(output)
|
|
||||||
|
|
||||||
except:
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def handleIPN(request, module):
|
def handleIPN(request, module):
|
||||||
|
@ -304,11 +278,6 @@ def handleIPN(request, module):
|
||||||
return HttpResponse("ipn")
|
return HttpResponse("ipn")
|
||||||
|
|
||||||
|
|
||||||
def paymentcomplete(request):
|
|
||||||
# pick up all get and post parameters and display
|
|
||||||
output = "payment complete"
|
|
||||||
output += request.method + "\n" + str(request.REQUEST.items())
|
|
||||||
return HttpResponse(output)
|
|
||||||
|
|
||||||
def checkStatus(request):
|
def checkStatus(request):
|
||||||
# Check the status of all PAY transactions and flag any errors
|
# Check the status of all PAY transactions and flag any errors
|
||||||
|
@ -322,5 +291,25 @@ def checkStatus(request):
|
||||||
def _render(request, template, template_vars={}):
|
def _render(request, template, template_vars={}):
|
||||||
return render_to_response(template, template_vars, RequestContext(request))
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Django==1.3.1
|
Django==1.4.1
|
||||||
MySQL-python==1.2.3
|
MySQL-python==1.2.3
|
||||||
south
|
south
|
||||||
django-extensions
|
django-extensions
|
||||||
|
@ -19,8 +19,8 @@ freebase
|
||||||
django-endless-pagination
|
django-endless-pagination
|
||||||
django-selectable
|
django-selectable
|
||||||
pytz
|
pytz
|
||||||
django-notification
|
|
||||||
git+ssh://git@github.com/Gluejar/boto.git@2.3.0
|
git+ssh://git@github.com/Gluejar/boto.git@2.3.0
|
||||||
|
https://github.com/aladagemre/django-notification/tarball/master
|
||||||
fabric
|
fabric
|
||||||
paramiko
|
paramiko
|
||||||
pyasn1
|
pyasn1
|
||||||
|
@ -28,3 +28,4 @@ pycrypto==2.5
|
||||||
django-maintenancemode
|
django-maintenancemode
|
||||||
django-smtp-ssl
|
django-smtp-ssl
|
||||||
django-ckeditor
|
django-ckeditor
|
||||||
|
stripe
|
||||||
|
|
|
@ -1,56 +1,52 @@
|
||||||
# requirements.pip does not specify versions of the libraries. Here's a modified version of the output from pip freeze
|
Django==1.4.1
|
||||||
# on March 7, 2012 for which the tests run successfully for https://github.com/Gluejar/regluit/commit/000d78dbd0e377d2b1688a4a1a333df49a0dd11b
|
Fabric==1.4.3
|
||||||
# If you were to run pip install -r requirements.pip anew, the tests no longer run perfectly because of the use of
|
MySQL-python==1.2.3
|
||||||
# some later libraries
|
Pillow==1.7.7
|
||||||
|
Pyzotero==0.9.51
|
||||||
|
South==0.7.6
|
||||||
amqplib==1.0.2
|
amqplib==1.0.2
|
||||||
anyjson==0.3.1
|
anyjson==0.3.3
|
||||||
boto==2.3.0
|
billiard==2.7.3.12
|
||||||
celery==2.4.6
|
#boto==2.3.0
|
||||||
certifi==0.0.6
|
git+ssh://git@github.com/Gluejar/boto.git@2.3.0
|
||||||
django-celery==2.4.2
|
celery==3.0.9
|
||||||
|
distribute==0.6.28
|
||||||
|
django-celery==3.0.9
|
||||||
django-ckeditor==3.6.2.1
|
django-ckeditor==3.6.2.1
|
||||||
django-debug-toolbar==0.8.5
|
|
||||||
django-endless-pagination==1.1
|
django-endless-pagination==1.1
|
||||||
django-extensions==0.7.1
|
django-extensions==0.9
|
||||||
django-kombu==0.9.4
|
django-kombu==0.9.4
|
||||||
django-maintenancemode==0.10
|
django-maintenancemode==0.10
|
||||||
django-nose-selenium==0.7.3
|
django-nose-selenium==0.7.3
|
||||||
django-notification==0.2
|
#django-notification==0.2
|
||||||
django-picklefield==0.1.9
|
git+git://github.com/aladagemre/django-notification.git@2927346f4c513a217ac8ad076e494dd1adbf70e1
|
||||||
django-profiles==0.2
|
django-registration==0.8
|
||||||
https://bitbucket.org/ubernostrum/django-registration/get/tip.tar.gz
|
django-selectable==0.5.2
|
||||||
#django-registration==0.8-alpha-1
|
|
||||||
django-selectable==0.2
|
|
||||||
django-smtp-ssl==1.0
|
django-smtp-ssl==1.0
|
||||||
django-social-auth==0.6.1
|
django-social-auth==0.7.5
|
||||||
#https://github.com/toastdriven/django-tastypie/tarball/master
|
|
||||||
django-tastypie==0.9.11
|
django-tastypie==0.9.11
|
||||||
Django==1.3.1
|
feedparser==5.1.2
|
||||||
Fabric==1.4.1
|
|
||||||
feedparser==5.1
|
|
||||||
freebase==1.0.8
|
freebase==1.0.8
|
||||||
httplib2==0.7.2
|
httplib2==0.7.5
|
||||||
kombu==1.5.1
|
kombu==2.4.5
|
||||||
# lxml requires special handling to install properly
|
lxml==3.0alpha2
|
||||||
#lxml==2.3.1
|
|
||||||
mechanize==0.2.5
|
mechanize==0.2.5
|
||||||
mimeparse==0.1.3
|
mimeparse==0.1.3
|
||||||
MySQL-python==1.2.3
|
|
||||||
nose==1.1.2
|
nose==1.1.2
|
||||||
oauth2==1.5.211
|
oauth2==1.5.211
|
||||||
paramiko==1.7.7.1
|
paramiko==1.7.7.2
|
||||||
pyasn1==0.1.3
|
pyasn1==0.1.4
|
||||||
pycrypto==2.5
|
pycrypto==2.6
|
||||||
pyparsing==1.5.6
|
python-dateutil==2.1
|
||||||
python-dateutil==1.5
|
|
||||||
python-digest==1.7
|
|
||||||
python-openid==2.2.5
|
python-openid==2.2.5
|
||||||
pytz==2012b
|
pytz==2012d
|
||||||
Pyzotero==0.9.4
|
rdflib==2.4.0
|
||||||
rdflib==3.1.0
|
redis==2.6.2
|
||||||
redis==2.4.11
|
requests==0.14.0
|
||||||
requests==0.9.1
|
selenium==2.25.0
|
||||||
selenium==2.24.0
|
six==1.2.0
|
||||||
South==0.7.3
|
ssh==1.7.14
|
||||||
ssh==1.7.13
|
stripe==1.7.4
|
||||||
|
virtualenv==1.4.9
|
||||||
|
virtualenvwrapper==2.2.2
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
|
@ -49,7 +49,7 @@ STATIC_URL = '/static/'
|
||||||
# URL prefix for admin static files -- CSS, JavaScript and images.
|
# URL prefix for admin static files -- CSS, JavaScript and images.
|
||||||
# Make sure to use a trailing slash.
|
# Make sure to use a trailing slash.
|
||||||
# Examples: "http://foo.com/static/admin/", "/static/admin/".
|
# Examples: "http://foo.com/static/admin/", "/static/admin/".
|
||||||
ADMIN_MEDIA_PREFIX = '/static/admin/'
|
# ADMIN_MEDIA_PREFIX = '/static/admin/'
|
||||||
|
|
||||||
# Additional locations of static files
|
# Additional locations of static files
|
||||||
STATICFILES_DIRS = (
|
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
|
# 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.
|
# 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
|
# a SECRET_KEY to be used for encrypting values in core.models.Key -- you should store in settings/local.py
|
||||||
SECRET_KEY = ''
|
SECRET_KEY = ''
|
||||||
|
@ -286,3 +282,8 @@ SECRET_KEY = ''
|
||||||
MAINTENANCE_MODE = False
|
MAINTENANCE_MODE = False
|
||||||
# Sequence of URL path regexes to exclude from the maintenance mode.
|
# Sequence of URL path regexes to exclude from the maintenance mode.
|
||||||
MAINTENANCE_IGNORE_URLS = {}
|
MAINTENANCE_IGNORE_URLS = {}
|
||||||
|
|
||||||
|
class NONPROFIT:
|
||||||
|
is_on = True
|
||||||
|
name = 'Library Renewal'
|
||||||
|
link = 'http://127.0.0.1:8000/donate_to_campaign/'
|
|
@ -102,8 +102,8 @@
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-top-color: #FFFFFF;
|
border-top-color: #FFF;
|
||||||
border-bottom-color: #FFFFFF;
|
border-bottom-color: #FFF;
|
||||||
}
|
}
|
||||||
.panelhoverlink {
|
.panelhoverlink {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
width: 118px;
|
width: 118px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
padding: 0px 0px;
|
padding: 0px 0px;
|
||||||
background: #FFFFFF;
|
background: #FFF;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
-moz-border-radius: 4px;
|
-moz-border-radius: 4px;
|
||||||
-webkit-border-radius: 4px;
|
-webkit-border-radius: 4px;
|
||||||
|
@ -178,6 +178,9 @@
|
||||||
color: #3d4e53;
|
color: #3d4e53;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.panelview.book-name div a {
|
||||||
|
color: #6994a3;
|
||||||
|
}
|
||||||
.panelview.booklist-status {
|
.panelview.booklist-status {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -276,7 +279,7 @@ div.panelview.side2 {
|
||||||
width: 118px;
|
width: 118px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
padding: 0px 0px;
|
padding: 0px 0px;
|
||||||
background: #FFFFFF;
|
background: #FFF;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
-moz-border-radius: 4px;
|
-moz-border-radius: 4px;
|
||||||
-webkit-border-radius: 4px;
|
-webkit-border-radius: 4px;
|
||||||
|
@ -330,7 +333,7 @@ div.panelview.side2 {
|
||||||
width: 118px;
|
width: 118px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
padding: 0px 0px;
|
padding: 0px 0px;
|
||||||
background: #FFFFFF;
|
background: #FFF;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
-moz-border-radius: 4px;
|
-moz-border-radius: 4px;
|
||||||
-webkit-border-radius: 4px;
|
-webkit-border-radius: 4px;
|
||||||
|
@ -356,7 +359,7 @@ div.panelview.side2 {
|
||||||
width: 118px;
|
width: 118px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
padding: 0px 0px;
|
padding: 0px 0px;
|
||||||
background: #FFFFFF;
|
background: #FFF;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
-moz-border-radius: 4px;
|
-moz-border-radius: 4px;
|
||||||
-webkit-border-radius: 4px;
|
-webkit-border-radius: 4px;
|
||||||
|
@ -388,8 +391,8 @@ div.panelview.side2 {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-top-color: #FFFFFF;
|
border-top-color: #FFF;
|
||||||
border-bottom-color: #FFFFFF;
|
border-bottom-color: #FFF;
|
||||||
background: url("/static/images/book-panel/add_wish_icon.png") no-repeat left center;
|
background: url("/static/images/book-panel/add_wish_icon.png") no-repeat left center;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
@ -423,8 +426,8 @@ div.panelview.side2 {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-top-color: #FFFFFF;
|
border-top-color: #FFF;
|
||||||
border-bottom-color: #FFFFFF;
|
border-bottom-color: #FFF;
|
||||||
background: url("/static/images/booklist/remove-wishlist-white.png") no-repeat left center;
|
background: url("/static/images/booklist/remove-wishlist-white.png") no-repeat left center;
|
||||||
}
|
}
|
||||||
.moreinfo.remove-wishlist a,
|
.moreinfo.remove-wishlist a,
|
||||||
|
@ -453,8 +456,8 @@ div.panelview.side2 {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-top-color: #FFFFFF;
|
border-top-color: #FFF;
|
||||||
border-bottom-color: #FFFFFF;
|
border-bottom-color: #FFF;
|
||||||
background: url("/static/images/checkmark_small-white.png") no-repeat left center;
|
background: url("/static/images/checkmark_small-white.png") no-repeat left center;
|
||||||
}
|
}
|
||||||
.moreinfo.on-wishlist a,
|
.moreinfo.on-wishlist a,
|
||||||
|
@ -482,7 +485,7 @@ div.panelview.side2 {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
.white_text a {
|
.white_text a {
|
||||||
color: #FFFFFF;
|
color: #FFF;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.white_text a:hover {
|
.white_text a:hover {
|
||||||
|
@ -505,8 +508,8 @@ div.panelview.side2 {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-top-color: #FFFFFF;
|
border-top-color: #FFF;
|
||||||
border-bottom-color: #FFFFFF;
|
border-bottom-color: #FFF;
|
||||||
background: url("/static/images/book-panel/more_icon.png") no-repeat left center;
|
background: url("/static/images/book-panel/more_icon.png") no-repeat left center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
margin: 10px;
|
margin: 10px 0 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.pledge_amount {
|
.pledge_amount {
|
||||||
|
@ -149,7 +149,7 @@ p {
|
||||||
}
|
}
|
||||||
#fakepledgesubmit {
|
#fakepledgesubmit {
|
||||||
background-color: #e35351;
|
background-color: #e35351;
|
||||||
cursor: pointer;
|
cursor: default;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -157,3 +157,59 @@ p {
|
||||||
span.menu-item-price {
|
span.menu-item-price {
|
||||||
float: none !important;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -4,47 +4,154 @@ $j(function() {
|
||||||
$j('#id_preapproval_amount').focus();
|
$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() {
|
$j().ready(function() {
|
||||||
// cache these to speed things up
|
// cache these to speed things up
|
||||||
var inputbox = $j('#id_preapproval_amount');
|
var inputbox = $j('#id_preapproval_amount');
|
||||||
var submitbutton = $j('#pledgesubmit');
|
var submitbutton = $j('#pledgesubmit');
|
||||||
var fakesubmitbutton = $j('#fakepledgesubmit');
|
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) {
|
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.siblings('span.menu-item-price').html();
|
||||||
amt = amt.split('$')[1];
|
amt = amt.split('$')[1];
|
||||||
amt = parseInt(amt);
|
amt = parseInt(amt);
|
||||||
return 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() {
|
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'});
|
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();
|
fakesubmitbutton.show();
|
||||||
submitbutton.hide();
|
submitbutton.hide();
|
||||||
submitbutton.attr('disabled', 'disabled');
|
submitbutton.attr('disabled', 'disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when pledge covers premium again, revert to original styling and reactivate button
|
||||||
var allclear = function() {
|
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'});
|
inputbox.css({'border-color': '#8dc63f', 'background-color': 'white', 'color': '#3d4e53'});
|
||||||
fakesubmitbutton.hide();
|
fakesubmitbutton.hide();
|
||||||
submitbutton.show();
|
submitbutton.show();
|
||||||
submitbutton.removeAttr('disabled');
|
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
|
// 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));
|
amount = canonicalize($j(this));
|
||||||
current = inputbox.val();
|
current = inputbox.val();
|
||||||
if (current<amount) {
|
if (current<amount) {
|
||||||
|
@ -54,9 +161,9 @@ $j().ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
inputbox.keyup(function() {
|
|
||||||
// when user changes the pledge box contents, ensure they are compatible
|
// when user changes the pledge box contents, ensure they are compatible
|
||||||
// with the selected pledge
|
// with the selected pledge
|
||||||
|
inputbox.keyup(function() {
|
||||||
current = $j(this).val();
|
current = $j(this).val();
|
||||||
|
|
||||||
if (current[0] == '$') {
|
if (current[0] == '$') {
|
||||||
|
@ -65,11 +172,41 @@ $j().ready(function() {
|
||||||
$j(this).val(current);
|
$j(this).val(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
amount = canonicalize($j('input[type=radio]:checked'));
|
amount = canonicalize($j('input[type=radio]:checked'));
|
||||||
if (current<amount) {
|
} catch(error) {
|
||||||
|
amount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current<amount && amount !=0) {
|
||||||
mayday();
|
mayday();
|
||||||
} else if (submitbutton.attr('disabled')) {
|
} else if (submitbutton.attr('disabled')) {
|
||||||
allclear();
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -23,8 +23,8 @@
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-top-color: #FFFFFF;
|
border-top-color: #FFF;
|
||||||
border-bottom-color: #FFFFFF;
|
border-bottom-color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelhoverlink {
|
.panelhoverlink {
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
width:118px;
|
width:118px;
|
||||||
height:35px;
|
height:35px;
|
||||||
padding:0px 0px;
|
padding:0px 0px;
|
||||||
background:#FFFFFF;
|
background:#FFF;
|
||||||
margin:0px;
|
margin:0px;
|
||||||
.one-border-radius(4px);
|
.one-border-radius(4px);
|
||||||
border: 1px solid #81bb38;
|
border: 1px solid #81bb38;
|
||||||
|
@ -127,8 +127,12 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height:16px;
|
line-height:16px;
|
||||||
max-height:32px;
|
max-height:32px;
|
||||||
color: #3d4e53;
|
color: @text-blue;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: @medium-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelview.booklist-status {
|
.panelview.booklist-status {
|
||||||
|
@ -280,7 +284,7 @@ div.panelview.side2 {
|
||||||
margin:0px;
|
margin:0px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color:#FFFFFF;
|
color:#FFF;
|
||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
|
|
||||||
&:hover { .panelhoverlink;}
|
&:hover { .panelhoverlink;}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
input[type="submit"] {
|
input[type="submit"] {
|
||||||
float: right;
|
float: right;
|
||||||
font-size: @font-size-header;
|
font-size: @font-size-header;
|
||||||
margin: 10px;
|
margin: 10px 0 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ p {
|
||||||
|
|
||||||
#fakepledgesubmit {
|
#fakepledgesubmit {
|
||||||
background-color: @alert;
|
background-color: @alert;
|
||||||
cursor: pointer;
|
cursor: default;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: @font-size-header;
|
font-size: @font-size-header;
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -77,3 +77,69 @@ p {
|
||||||
span.menu-item-price {
|
span.menu-item-price {
|
||||||
float: none !important;
|
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;
|
||||||
|
}
|
|
@ -13,3 +13,80 @@
|
||||||
border-style: solid none;
|
border-style: solid none;
|
||||||
border-color: #FFFFFF;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(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);
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -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;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Loading…
Reference in New Issue