diff --git a/core/models.py b/core/models.py index 010c9e27..91fe8e85 100755 --- a/core/models.py +++ b/core/models.py @@ -18,7 +18,10 @@ import regluit.core.isbn from regluit.core.signals import successful_campaign, unsuccessful_campaign import binascii -from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE +from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE + +from django.db.models import Q + class UnglueitError(RuntimeError): pass @@ -323,7 +326,26 @@ class Campaign(models.Model): def supporters_count(self): # avoid transmitting the whole list if you don't need to; let the db do the count. return self.transactions().filter(status=TRANSACTION_STATUS_ACTIVE).values_list('user', flat=True).distinct().count() + + def transaction_to_recharge(self, user): + """given a user, return the transaction to be recharged if there is one -- None otherwise""" + # only if a campaign is SUCCESSFUL, we allow for recharged + + if self.status == 'SUCCESSFUL': + if self.transaction_set.filter(Q(user=user) & (Q(status=TRANSACTION_STATUS_COMPLETE) | Q(status=TRANSACTION_STATUS_ACTIVE))).count(): + # presence of an active or complete transaction means no transaction to recharge + return None + else: + transactions = self.transaction_set.filter(Q(user=user) & (Q(status=TRANSACTION_STATUS_ERROR) | Q(status=TRANSACTION_STATUS_FAILED))) + # assumption --that the first failed/errored transaction has the amount we need to recharge + if transactions.count(): + return transactions[0] + else: + return None + else: + return None + def ungluers(self): p = PaymentManager() ungluers={"all":[],"supporters":[], "patrons":[], "bibliophiles":[]} @@ -804,7 +826,21 @@ class UserProfile(models.Model): goodreads_auth_token = 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) - + +#class CampaignSurveyResponse(models.Model): +# # generic +# campaign = models.ForeignKey("Campaign", related_name="surveyresponse", null=False) +# user = models.OneToOneField(User, related_name='surveyresponse') +# transaction = models.ForeignKey("payment.Transaction", null=True) +# # for OLA only +# premium = models.ForeignKey("Premium", null=True) +# anonymous = models.BooleanField(null=False) +# # relevant to all campaigns since these arise from acknowledgement requirements from generic premiums +# name = models.CharField(max_length=140, blank=True) +# url = models.URLField(blank=True) +# tagline = models.CharField(max_length=140, blank=True) +# # do we need to collect address for Rupert or will he do that once he has emails? + # this was causing a circular import problem and we do not seem to be using # anything from regluit.core.signals after this line # from regluit.core import signals diff --git a/fabfile.py b/fabfile.py index 5f7bd7cb..91f61cc1 100644 --- a/fabfile.py +++ b/fabfile.py @@ -87,7 +87,9 @@ def public_key_from_private_key(): def email_addresses(): """list email addresses in unglue.it""" with cd("/opt/regluit"): - run("""source ENV/bin/activate; echo "import django; print ' \\n'.join([u.email for u in django.contrib.auth.models.User.objects.all() ]); quit()" | django-admin.py shell_plus --settings=regluit.settings.me""") + run("""source ENV/bin/activate; echo "import django; print ' \\n'.join([u.email for u in django.contrib.auth.models.User.objects.all() ]); quit()" | django-admin.py shell_plus --settings=regluit.settings.me > /home/ubuntu/emails.txt""") + local("scp web1:/home/ubuntu/emails.txt .") + run("""rm /home/ubuntu/emails.txt""") def selenium(): """setting up selenium to run in the background on RY's laptop""" diff --git a/frontend/templates/base.html b/frontend/templates/base.html index 31498d41..61f6d1dd 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -85,7 +85,7 @@
-The first unglued book is on the way! Please consider supporting our three active campaigns. +The first unglued book is on the way! Please consider supporting our four active campaigns.
{% block topsection %}{% endblock %} {% block content %}{% endblock %} diff --git a/frontend/urls.py b/frontend/urls.py index 73b5766e..05bcd120 100644 --- a/frontend/urls.py +++ b/frontend/urls.py @@ -7,7 +7,7 @@ from django.conf import settings from regluit.core.feeds import SupporterWishlistFeed from regluit.core.models import Campaign -from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeModifyView, PledgeCancelView, PledgeNeverMindView, FAQView +from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeModifyView, PledgeCancelView, PledgeNeverMindView, PledgeRechargeView, FAQView from regluit.frontend.views import CampaignListView, DonateView, WorkListView, UngluedListView, InfoPageView urlpatterns = patterns( @@ -55,6 +55,7 @@ urlpatterns = patterns( url(r"^pledge/complete/$", login_required(PledgeCompleteView.as_view()), name="pledge_complete"), url(r"^pledge/nevermind/$", login_required(PledgeNeverMindView.as_view()), name="pledge_nevermind"), url(r"^pledge/modify/(?P\d+)$", login_required(PledgeModifyView.as_view()), name="pledge_modify"), + url(r"^pledge/recharge/(?P\d+)$", login_required(PledgeRechargeView.as_view()), name="pledge_recharge"), url(r"^subjects/$", "subjects", name="subjects"), url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"), url(r"^librarything/load/$","librarything_load", name="librarything_load"), diff --git a/frontend/views.py b/frontend/views.py index 5e707ad5..64da339e 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -674,7 +674,8 @@ class PledgeView(FormView): class PledgeModifyView(FormView): """ A view to handle request to change an existing pledge - """ + """ + template_name="pledge.html" form_class = CampaignPledgeForm embedded = False @@ -798,6 +799,58 @@ class PledgeModifyView(FormView): return HttpResponse("No modification made") + +class PledgeRechargeView(TemplateView): + """ + a view to allow for recharge of a transaction for failed transactions or ones with errors + """ + template_name="pledge_recharge.html" + + def get_context_data(self, **kwargs): + + context = super(PledgeRechargeView, self).get_context_data(**kwargs) + + # the following should be true since PledgeModifyView.as_view is wrapped in login_required + assert self.request.user.is_authenticated() + user = self.request.user + + work = get_object_or_404(models.Work, id=self.kwargs["work_id"]) + campaign = work.last_campaign() + + if campaign is None: + return Http404 + + transaction = campaign.transaction_to_recharge(user) + + # calculate a URL to do a preapproval -- in the future, we may want to do a straight up payment + + return_url = None + nevermind_url = None + + if transaction is not None: + # the recipients of this authorization is not specified here but rather by the PaymentManager. + # set the expiry date based on the campaign deadline + expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) + + paymentReason = "Unglue.it Recharge for {0}".format(campaign.name) + + p = PaymentManager(embedded=False) + t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, transaction.amount, expiry=expiry, campaign=campaign, list=None, user=user, + return_url=return_url, nevermind_url=nevermind_url, anonymous=transaction.anonymous, premium=transaction.premium, + paymentReason=paymentReason) + logger.info("Recharge url: {0}".format(url)) + else: + url = None + + context.update({ + 'work':work, + 'transaction':transaction, + 'payment_processor':transaction.host if transaction is not None else None, + 'recharge_url': url + }) + return context + + class PledgeCompleteView(TemplateView): """A callback for PayPal to tell unglue.it that a payment transaction has completed successfully. diff --git a/payment/amazon.py b/payment/amazon.py index 3b3dde3f..0349f075 100644 --- a/payment/amazon.py +++ b/payment/amazon.py @@ -220,14 +220,36 @@ def amazonPaymentReturn(request): status = request.GET['status'] reference = request.GET['callerReference'] token = request.GET['tokenID'] - - # BUGUBG - Should we verify the signature here? - # + # 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 - # We will catch the exception if it does not exist - # - transaction = Transaction.objects.get(secret=reference) + 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)