diff --git a/frontend/templates/pledge.html b/frontend/templates/pledge.html index c545fa8a..184f20da 100644 --- a/frontend/templates/pledge.html +++ b/frontend/templates/pledge.html @@ -7,13 +7,7 @@ - + {% endblock %} {% block doccontent %} diff --git a/payment/amazon.py b/payment/amazon.py index 6f4f1553..fde19710 100644 --- a/payment/amazon.py +++ b/payment/amazon.py @@ -57,10 +57,14 @@ try: FPS_SECRET_KEY = Key.objects.get(name="FPS_SECRET_KEY").value logger.info('Successful loading of FPS_*_KEYs') except Exception, e: - FPS_ACCESS_KEY = '' - FPS_SECRET_KEY = '' 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): ''' @@ -89,6 +93,7 @@ def ProcessIPN(request): ''' try: logging.debug("Amazon IPN called") + logging.debug(request.POST) uri = request.build_absolute_uri() parsed_url = urlparse.urlparse(uri) @@ -338,7 +343,6 @@ class AmazonRequest: def success(self): - print "CALLING SUCCESS" if self.errorMessage: return False else: @@ -483,7 +487,7 @@ class Execute(AmazonRequest): 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, @@ -495,7 +499,8 @@ class Execute(AmazonRequest): callerDescription=None, metadata=None, transactionDate=None, - reserve=False) + reserve=False, + extra_params=global_params) # # BUGBUG: @@ -653,14 +658,15 @@ class CancelPreapproval(AmazonRequest): self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST) self.transaction = transaction - params = {} + 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() - print body + if(fps_response.status == 200): rs = ResultSet() @@ -716,7 +722,8 @@ class RefundPayment(AmazonRequest): # 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.refund(transaction.secret, transaction.preapproval_key) + 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:") diff --git a/payment/manager.py b/payment/manager.py index fb90b3e8..7645cd97 100644 --- a/payment/manager.py +++ b/payment/manager.py @@ -24,7 +24,6 @@ import urllib, urlparse from django.conf import settings - logger = logging.getLogger(__name__) def append_element(doc, parent, name, text): @@ -42,15 +41,18 @@ class PaymentManager( object ): def __init__( self, embedded=False): self.embedded = embedded - def processIPN(self, request): + def processIPN(self, request, module): # Forward to our payment processor - return ProcessIPN(request) + mod = __import__("regluit.payment." + module, fromlist=[str(module)]) + method = getattr(mod, "ProcessIPN") + return method(request) def update_preapproval(self, transaction): """Update a transaction to hold the data from a PreapprovalDetails on that transaction""" t = transaction - p = PreapprovalDetails(t) + method = getattr(transaction.get_payment_class(), "PreapprovalDetails") + p = method(t) preapproval_status = {'id':t.id, 'key':t.preapproval_key} @@ -102,7 +104,8 @@ class PaymentManager( object ): t = transaction payment_status = {'id':t.id} - p = PaymentDetails(t) + method = getattr(transaction.get_payment_class(), "PaymentDetails") + p = method(t) if p.error() or not p.success(): logger.info("Error retrieving payment details for transaction %d" % t.id) @@ -422,7 +425,8 @@ class PaymentManager( object ): transaction.date_executed = now() transaction.save() - p = Finish(transaction) + method = getattr(transaction.get_payment_class(), "Finish") + p = method(transaction) # Create a response for this envelope = p.envelope() @@ -476,7 +480,8 @@ class PaymentManager( object ): transaction.date_payment = now() transaction.save() - p = Execute(transaction) + method = getattr(transaction.get_payment_class(), "Execute") + p = method(transaction) # Create a response for this envelope = p.envelope() @@ -515,7 +520,8 @@ class PaymentManager( object ): return value: True if successful, false otherwise ''' - p = CancelPreapproval(transaction) + method = getattr(transaction.get_payment_class(), "CancelPreapproval") + p = method(transaction) # Create a response for this envelope = p.envelope() @@ -593,8 +599,9 @@ class PaymentManager( object ): urllib.urlencode({'tid':t.id})) return_url = urlparse.urljoin(settings.BASE_URL, return_path) - p = Preapproval(t, amount, expiry, return_url=return_url, cancel_url=cancel_url, paymentReason=paymentReason) - + method = getattr(t.get_payment_class(), "Preapproval") + p = method(t, amount, expiry, return_url=return_url, cancel_url=cancel_url, paymentReason=paymentReason) + # Create a response for this envelope = p.envelope() @@ -716,7 +723,8 @@ class PaymentManager( object ): logger.info("Refund Transaction failed, invalid transaction status") return False - p = RefundPayment(transaction) + method = getattr(transaction.get_payment_class(), "RefundPayment") + p = method(transaction) # Create a response for this envelope = p.envelope() @@ -791,8 +799,8 @@ class PaymentManager( object ): ) t.create_receivers(receiver_list) - - p = Pay(t,return_url=return_url, cancel_url=cancel_url) + method = getattr(t.get_payment_class(), "Pay") + p = method(t,return_url=return_url, cancel_url=cancel_url) # Create a response for this envelope = p.envelope() @@ -825,4 +833,4 @@ class PaymentManager( object ): logger.info("Pledge Error: %s" % p.error_string()) return t, None - \ No newline at end of file + diff --git a/payment/migrations/0007_auto__add_field_transaction_host.py b/payment/migrations/0007_auto__add_field_transaction_host.py new file mode 100644 index 00000000..8400cbe5 --- /dev/null +++ b/payment/migrations/0007_auto__add_field_transaction_host.py @@ -0,0 +1,178 @@ +# 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 field 'Transaction.host' + db.add_column('payment_transaction', 'host', self.gf('django.db.models.fields.CharField')(default='amazon', max_length=32), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Transaction.host' + db.delete_column('payment_transaction', 'host') + + + 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': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'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'}, + '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'}), + 'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + 'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), + 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': '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.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'}), + '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.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'}, + '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': "'amazon'", 'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': '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'}), + 'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + } + } + + complete_apps = ['payment'] diff --git a/payment/models.py b/payment/models.py index 34e246b8..0ac60f8e 100644 --- a/payment/models.py +++ b/payment/models.py @@ -10,6 +10,9 @@ class Transaction(models.Model): # type e.g., PAYMENT_TYPE_INSTANT or PAYMENT_TYPE_AUTHORIZATION -- defined in parameters.py type = models.IntegerField(default=PAYMENT_TYPE_NONE, null=False) + # host: the payment processor. Named after the payment module that hosts the payment processing functions + host = models.CharField(default=PAYMENT_HOST_AMAZON, 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) @@ -87,6 +90,16 @@ class Transaction(models.Model): for r in receiver_list: receiver = Receiver.objects.create(email=r['email'], amount=r['amount'], currency=self.currency, status="None", primary=primary, transaction=self) primary = False + + def get_payment_class(self): + ''' + Returns the specific payment module that implements this transaction + ''' + if self.host == PAYMENT_HOST_NONE: + return None + else: + mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)]) + return mod class PaymentResponse(models.Model): diff --git a/payment/parameters.py b/payment/parameters.py index f3fb32a1..10c35d82 100644 --- a/payment/parameters.py +++ b/payment/parameters.py @@ -2,6 +2,10 @@ PAYMENT_TYPE_NONE = 0 PAYMENT_TYPE_INSTANT = 1 PAYMENT_TYPE_AUTHORIZATION = 2 +PAYMENT_HOST_NONE = "none" +PAYMENT_HOST_PAYPAL = "paypal" +PAYMENT_HOST_AMAZON = "amazon" + EXECUTE_TYPE_NONE = 0 EXECUTE_TYPE_CHAINED_INSTANT = 1 EXECUTE_TYPE_CHAINED_DELAYED = 2 diff --git a/payment/paypal.py b/payment/paypal.py index 153b09ca..3ca50903 100644 --- a/payment/paypal.py +++ b/payment/paypal.py @@ -336,7 +336,7 @@ class PaypalEnvelopeRequest: url = None def ack( self ): - + if self.response and self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ): return self.response['responseEnvelope']['ack'] else: @@ -344,6 +344,7 @@ class PaypalEnvelopeRequest: def success(self): status = self.ack() + # print status if status == "Success" or status == "SuccessWithWarning": return True @@ -401,8 +402,7 @@ class PaypalEnvelopeRequest: return self.response['responseEnvelope']['timestamp'] else: return None - - + class Pay( PaypalEnvelopeRequest ): def __init__( self, transaction, return_url=None, cancel_url=None, paymentReason=""): @@ -472,7 +472,7 @@ class Pay( PaypalEnvelopeRequest ): 'returnUrl': return_url, 'cancelUrl': cancel_url, 'requestEnvelope': { 'errorLanguage': 'en_US' }, - 'ipnNotificationUrl': settings.BASE_URL + reverse('HandleIPN'), + 'ipnNotificationUrl': settings.BASE_URL + reverse('HandleIPN', args=["paypal"]), 'feesPayer': feesPayer, 'trackingId': transaction.secret } @@ -536,7 +536,15 @@ class Pay( PaypalEnvelopeRequest ): return '%s/webapps/adaptivepayment/flow/pay?paykey=%s&expType=light' % ( settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] ) - +class Execute(Pay): + ''' + For payapl, execute is the same as pay. The pay funciton detects whether an execute or a co-branded operation + is called for. + ''' + def __init__(self, transaction, return_url=None, cancel_url=None): + # Call our super class. In python 2.2+, we can't use super here, so just call init directly + Pay.__init__(self, transaction, return_url, cancel_url) + class Finish(PaypalEnvelopeRequest): def __init__(self, transaction=None): @@ -817,7 +825,7 @@ class Preapproval( PaypalEnvelopeRequest ): 'returnUrl': return_url, 'cancelUrl': cancel_url, 'requestEnvelope': { 'errorLanguage': 'en_US' }, - 'ipnNotificationUrl': settings.BASE_URL + reverse('HandleIPN') + 'ipnNotificationUrl': settings.BASE_URL + reverse('HandleIPN', args=["paypal"]) } # Is ipnNotificationUrl being computed properly diff --git a/payment/urls.py b/payment/urls.py index 82d32ccc..1237773d 100644 --- a/payment/urls.py +++ b/payment/urls.py @@ -4,8 +4,7 @@ from django.conf import settings urlpatterns = patterns( "regluit.payment.views", - url(r"^paypalipn", "handleIPN", name="HandleIPN"), - url(r"^amazonipn", "handleIPN", name="HandleIPN"), + url(r"^handleipn/(?P\w+)$", "handleIPN", name="HandleIPN"), ) # Amazon payment URLs diff --git a/payment/views.py b/payment/views.py index 5fe977cf..2a44c9c2 100644 --- a/payment/views.py +++ b/payment/views.py @@ -26,7 +26,7 @@ import logging logger = logging.getLogger(__name__) # parameterize some test recipients -TEST_RECEIVERS = ['raymond.yee@gmail.com', 'Buyer6_1325742408_per@gmail.com'] +TEST_RECEIVERS = ['seller_1317463643_biz@gmail.com', 'buyer5_1325740224_per@gmail.com'] #TEST_RECEIVERS = ['seller_1317463643_biz@gmail.com', 'Buyer6_1325742408_per@gmail.com'] #TEST_RECEIVERS = ['glueja_1317336101_biz@gluejar.com', 'rh1_1317336251_biz@gluejar.com', 'RH2_1317336302_biz@gluejar.com'] @@ -248,7 +248,7 @@ def testPledge(request): receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':pledge_amount}] else: receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}] - + if campaign_id: campaign = Campaign.objects.get(id=int(campaign_id)) t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user, return_url=None) @@ -294,11 +294,11 @@ def runTests(request): traceback.print_exc() @csrf_exempt -def handleIPN(request): +def handleIPN(request, module): # Handler for paypal IPN notifications p = PaymentManager() - p.processIPN(request) + p.processIPN(request, module) logger.info(str(request.POST)) return HttpResponse("ipn") diff --git a/requirements.pip b/requirements.pip index bf80d120..c0399a32 100644 --- a/requirements.pip +++ b/requirements.pip @@ -20,9 +20,9 @@ django-endless-pagination django-selectable pytz django-notification -boto +git+ssh://git@github.com/Gluejar/boto.git@2.3.0 fabric -git+git://github.com/agiliq/merchant.git#egg=django-merchant +#git+git://github.com/agiliq/merchant.git#egg=django-merchant paramiko pyasn1 pycrypto diff --git a/settings/dev.py b/settings/dev.py index 20921ed5..c1056003 100644 --- a/settings/dev.py +++ b/settings/dev.py @@ -94,6 +94,8 @@ PAYPAL_NONPROFIT_PARTNER_EMAIL = "nppart_1318957063_per@gluejar.com" PAYPAL_TEST_RH_EMAIL = "rh1_1317336251_biz@gluejar.com" BASE_URL = 'http://0.0.0.0' +BASE_URL_SECURE = 'https://0.0.0.0' +IPN_SECURE_URL = True # use database as queuing service in development BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport" diff --git a/settings/just.py b/settings/just.py index e2bb2ac9..359879da 100644 --- a/settings/just.py +++ b/settings/just.py @@ -91,6 +91,8 @@ CELERYD_HIJACK_ROOT_LOGGER = False # BASE_URL is a hard-coding of the domain name for site and used for PayPal IPN # Next step to try https BASE_URL = 'http://just.unglueit.com' +BASE_URL_SECURE = 'https://just.unglueit.com' +IPN_SECURE_URL = False # use redis for production queue BROKER_TRANSPORT = "redis" diff --git a/settings/please.py b/settings/please.py index 8f7c667e..54b0b14f 100644 --- a/settings/please.py +++ b/settings/please.py @@ -90,6 +90,8 @@ CELERYD_HIJACK_ROOT_LOGGER = False # BASE_URL is a hard-coding of the domain name for site and used for PayPal IPN # Next step to try https BASE_URL = 'http://please.unglueit.com' +BASE_URL_SECURE = 'https://please.unglueit.com' +IPN_SECURE_URL = False # use redis for production queue BROKER_TRANSPORT = "redis" diff --git a/settings/prod.py b/settings/prod.py index b337f670..e9f894c6 100644 --- a/settings/prod.py +++ b/settings/prod.py @@ -92,6 +92,8 @@ CELERYD_HIJACK_ROOT_LOGGER = False # BASE_URL is a hard-coding of the domain name for site and used for PayPal IPN # Next step to try https BASE_URL = 'http://unglue.it' +BASE_URL_SECURE = 'https://unglue.it' +IPN_SECURE_URL = False # use redis for production queue BROKER_TRANSPORT = "redis" diff --git a/static/js/expand.js b/static/js/expand.js index 10c39e4d..39bd284c 100644 --- a/static/js/expand.js +++ b/static/js/expand.js @@ -8,7 +8,7 @@ $j(document).ready(function(){ $j('#js-topsection').css({"opacity": "0.15"}); $j('#main-container').css({"opacity": "0.15"}); $j('#js-rightcol').css({"visibility":"hidden"}); - $j('#expandable').css({"position": "absolute", "z-index": "100", "left":"50%", "margin-left": "-115px"}).fadeTo("slow", 1);; + $j('#expandable').css({"position": "absolute", "z-index": "100", "left":"50%", "margin-left": "-115px"}).fadeTo("slow", 1); }); $j('#collapser').click(function(){ $j('#js-topsection').fadeTo("slow", 1); diff --git a/static/js/reconcile_pledge.js b/static/js/reconcile_pledge.js new file mode 100644 index 00000000..e1df29f3 --- /dev/null +++ b/static/js/reconcile_pledge.js @@ -0,0 +1,67 @@ +var $j = jQuery.noConflict(); +// give pledge box focus +$j(function() { + $j('#id_preapproval_amount').focus(); +}); + +// if amount in pledge box is too small to qualify for premium, call attention to it +// and disable the input button with a helpful message +// when they fix it, revert to original styling and reactivate button + +$j().ready(function() { + // cache these to speed things up + var inputbox = $j('#id_preapproval_amount'); + var submitbutton = $j('#pledgesubmit'); + + var canonicalize = function(amt) { + // takes an input button from the premiums list + // finds the premium amount its associated the span class + // converts to usable integer form and returns + amt = amt.siblings('span.menu-item-price').html(); + amt = amt.split('$')[1]; + amt = parseInt(amt); + return amt; + } + + 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'}); + submitbutton.css({'background-color': '#e35351', 'cursor': 'default', 'font-weight': 'normal', 'font-size': '15px'}); + submitbutton.val("You must pledge at least $"+amount+" for that premium"); + submitbutton.attr('disabled', 'disabled'); + } + + 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'}); + submitbutton.css({'background-color': '#8dc63f', 'cursor': 'pointer', 'font-weight': 'bold', 'font-size': '17px'}); + submitbutton.val("Modify Pledge"); + submitbutton.removeAttr('disabled'); + } + + $j('#premiums_list input').on("click", function() { + // when user clicks a premium, ensure it is compatible with the pledge box amount + amount = canonicalize($j(this)); + current = inputbox.val(); + if (current