diff --git a/frontend/forms.py b/frontend/forms.py index c7859207..6c764aec 100644 --- a/frontend/forms.py +++ b/frontend/forms.py @@ -363,7 +363,6 @@ class CCForm(forms.Form): decimal_places=2, label="Pledge", ) - 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() ) diff --git a/frontend/templates/fund_the_pledge.html b/frontend/templates/fund_the_pledge.html index d49a44c3..3d119fe5 100644 --- a/frontend/templates/fund_the_pledge.html +++ b/frontend/templates/fund_the_pledge.html @@ -8,8 +8,53 @@ - + + + + {% endblock %} @@ -55,30 +100,31 @@

{% if request.user.credit.available %}

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 %}

- -
- {% csrf_token %} - {{ form.non_field_errors }} - {{ form.as_p }} - - -
+ +
+ {% csrf_token %} + {{ form.non_field_errors }} + {{ form.as_p }} +
+ + +
+
+ + +
+
+ + + / + +
+ +
- - {% endblock %} - diff --git a/frontend/views.py b/frontend/views.py index f7ed48cf..8a107d9a 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -53,7 +53,7 @@ from regluit.payment.manager import PaymentManager from regluit.payment.models import Transaction, Account from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE, TRANSACTION_STATUS_NONE, TRANSACTION_STATUS_MODIFIED from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION, PAYMENT_TYPE_INSTANT -from regluit.payment.parameters import PAYMENT_HOST_STRIPE +from regluit.payment.parameters import PAYMENT_HOST_STRIPE, PAYMENT_HOST_NONE from regluit.payment.credit import credit_transaction from regluit.core import goodreads from tastypie.models import ApiKey @@ -627,6 +627,7 @@ class PledgeView(FormView): 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"]) @@ -701,7 +702,7 @@ class PledgeView(FormView): return HttpResponse("No modification made") else: t, url = p.process_transaction('USD', form.cleaned_data["preapproval_amount"], - host = None, + host = PAYMENT_HOST_NONE, campaign=self.campaign, user=self.request.user, paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name), @@ -766,67 +767,43 @@ class FundPledgeView(FormView): # first pass -- we have a token -- also do more direct coupling to stripelib -- then move to # abstraction of payment.manager / payment.baseprocessor - # demonstrate two possibilities: 1) token -> charge or 2) token->customer->charge + # we should getting a stripe_token only if we had asked for CC data + # BUGBUG -- don't know whether transaction.host should be None -- but if it is, set to the + # default processor + + transaction = self.transaction + if transaction.host is None or transaction.host == PAYMENT_HOST_NONE: + transaction.host = settings.PAYMENT_PROCESSOR + stripe_token = form.cleaned_data["stripe_token"] preapproval_amount = form.cleaned_data["preapproval_amount"] - retain_cc_info = form.cleaned_data["retain_cc_info"] - - 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 - ) + logger.info('stripe_token:{0}, preapproval_amount:{1}'.format(stripe_token, preapproval_amount)) - account.save() - - charge = sc.create_charge(preapproval_amount, customer=customer, description="${0} for test / retain cc".format(preapproval_amount)) - + p = PaymentManager() + + # if we get a stripe_token, create a new stripe account + + try: + account = p.make_account(transaction.user, stripe_token, host=transaction.host) + logger.info('account.id: {0}'.format(account.id)) + except Exception, e: + raise e + + # GOAL: deactivate any older accounts associated with user + + # with the Account in hand, now authorize transaction + transaction.amount = preapproval_amount + t, url = p.authorize(transaction) + logger.info("t, url: {0} {1}".format(t, url)) + + # redirecting user to pledge_complete on successful preapproval (in the case of stripe) + # BUGBUG: Make sure we are testing properly for successful authorization properly here + if url is not None: + return HttpResponseRedirect(url) else: - 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)) + return HttpResponse("preapproval_key: {0}".format(transaction.preapproval_key)) class NonprofitCampaign(FormView): diff --git a/payment/baseprocessor.py b/payment/baseprocessor.py index 138c3ec0..b166a8c5 100644 --- a/payment/baseprocessor.py +++ b/payment/baseprocessor.py @@ -1,4 +1,5 @@ -from regluit.payment.models import Transaction, PaymentResponse +from regluit.payment.models import PaymentResponse + from django.http import HttpResponseForbidden from datetime import timedelta from regluit.utils.localdatetime import now, zuluformat @@ -7,11 +8,6 @@ import datetime import time - -def ProcessIPN(request): - return HttpResponseForbidden() - - class BasePaymentRequest: ''' Handles common information incident to payment processing @@ -63,120 +59,131 @@ class BasePaymentRequest: def timestamp(self): return str(datetime.datetime.now()) +class Processor: + """a function that returns for the given payment processor""" + requires_explicit_preapprovals=False -class Pay( BasePaymentRequest ): - - ''' - The pay function generates a redirect URL to approve the transaction - ''' + def make_account(self, user, token): + """template function for return a payment.Account corresponding to the payment system""" + return None - 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): + def ProcessIPN(self, request): + return HttpResponseForbidden() - 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 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 - - -class CancelPreapproval(BasePaymentRequest): - ''' - Cancels an exisiting token. - ''' + def api(self): + return "null api" + + def exec_status( self ): + return None + + def amount( self ): + return None + + def key( self ): + return None - def __init__(self, transaction): - self.transaction = transaction - - -class RefundPayment(BasePaymentRequest): + 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 + + - def __init__(self, transaction): - self.transaction = transaction + class Finish(BasePaymentRequest): + ''' + The Finish function handles the secondary receiver in a chained payment. + ''' + def __init__(self, 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 - \ No newline at end of file + 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 + \ No newline at end of file diff --git a/payment/credit.py b/payment/credit.py index 4976089d..c6cf52ec 100644 --- a/payment/credit.py +++ b/payment/credit.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User from django.conf import settings from regluit.payment.parameters import * +from regluit.payment import baseprocessor from regluit.payment.baseprocessor import BasePaymentRequest @@ -28,28 +29,29 @@ def credit_transaction(t,user,amount): user.credit.add_to_pledged(pledge_amount) t.set_credit_approved(pledge_amount) -class CancelPreapproval(BasePaymentRequest): - ''' - Cancels an exisiting token. - ''' +class Processor(baseprocessor.Processor): + 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' - 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 + 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 diff --git a/payment/forms.py b/payment/forms.py index db65a947..764eb7e0 100644 --- a/payment/forms.py +++ b/payment/forms.py @@ -4,4 +4,4 @@ import logging logger = logging.getLogger(__name__) class StripePledgeForm(forms.Form): - stripe_token = forms.CharField(required=False, widget=forms.HiddenInput()) + stripeToken = forms.CharField(required=False, widget=forms.HiddenInput()) diff --git a/payment/management/commands/test_stripe_charge.py b/payment/management/commands/test_stripe_charge.py new file mode 100644 index 00000000..24bf035e --- /dev/null +++ b/payment/management/commands/test_stripe_charge.py @@ -0,0 +1,16 @@ +from django.core.management.base import BaseCommand +from regluit.payment import stripelib +from decimal import Decimal as D + +class Command(BaseCommand): + help = "create a credit card record and charge it -- for testing" + + def handle(self, *args, **kwargs): + # test card + sc = stripelib.StripeClient() + card = stripelib.card(number="4242424242424242", exp_month="01", exp_year="2013", cvc="123") + cust = sc.create_customer(card=card, description="William Shakespeare XIV (via test_stripe_charge)", email="bill.shakespeare@gmail.com") + print cust + # let's charge RY $1.00 + charge = sc.create_charge(D('1.00'), customer=cust.id, description="$1 TEST CHARGE for Will S. XIV") + print charge \ No newline at end of file diff --git a/payment/manager.py b/payment/manager.py index 64389779..7eadc79e 100644 --- a/payment/manager.py +++ b/payment/manager.py @@ -1,4 +1,3 @@ -from regluit.core.models import Campaign, Wishlist from regluit.payment.models import Transaction, Receiver, PaymentResponse from django.contrib.auth.models import User from django.core.urlresolvers import reverse @@ -7,8 +6,6 @@ from regluit.payment.parameters import * from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created from regluit.payment import credit -from regluit.payment.baseprocessor import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment - import uuid import traceback from regluit.utils.localdatetime import now @@ -40,14 +37,12 @@ class PaymentManager( object ): # Forward to our payment processor mod = __import__("regluit.payment." + module, fromlist=[str(module)]) - method = getattr(mod, "ProcessIPN") - return method(request) + return mod.Processor().ProcessIPN(request) def update_preapproval(self, transaction): """Update a transaction to hold the data from a PreapprovalDetails on that transaction""" t = transaction - method = getattr(transaction.get_payment_class(), "PreapprovalDetails") - p = method(t) + p = transaction.get_payment_class().PreapprovalDetails(t) preapproval_status = {'id':t.id, 'key':t.preapproval_key} @@ -99,8 +94,7 @@ class PaymentManager( object ): t = transaction payment_status = {'id':t.id} - method = getattr(transaction.get_payment_class(), "PaymentDetails") - p = method(t) + p = transaction.get_payment_class().PaymentDetails(t) if p.error() or not p.success(): logger.info("Error retrieving payment details for transaction %d" % t.id) @@ -414,8 +408,7 @@ class PaymentManager( object ): transaction.date_executed = now() transaction.save() - method = getattr(transaction.get_payment_class(), "Finish") - p = method(transaction) + p = transaction.get_payment_class().Finish(transaction) # Create a response for this envelope = p.envelope() @@ -469,8 +462,7 @@ class PaymentManager( object ): transaction.date_payment = now() transaction.save() - method = getattr(transaction.get_payment_class(), "Execute") - p = method(transaction) + p = transaction.get_payment_class().Execute(transaction) # Create a response for this envelope = p.envelope() @@ -509,32 +501,43 @@ class PaymentManager( object ): return value: True if successful, false otherwise ''' - method = getattr(transaction.get_payment_class(), "CancelPreapproval") - p = method(transaction) - - # Create a response for this - envelope = p.envelope() - - if envelope: - - correlation = p.correlation_id() - timestamp = p.timestamp() - - r = PaymentResponse.objects.create(api=p.url, - correlation_id = correlation, - timestamp = timestamp, - info = p.raw_response, - transaction=transaction) - - if p.success() and not p.error(): - logger.info("Cancel Transaction " + str(transaction.id) + " Completed") - return True + # does this transaction explicity require preapprovals? + requires_explicit_preapprovals = transaction.get_payment_class().requires_explicit_preapprovals + if requires_explicit_preapprovals: + + p = transaction.get_payment_class().CancelPreapproval(transaction) + + # Create a response for this + envelope = p.envelope() + + if envelope: + + correlation = p.correlation_id() + timestamp = p.timestamp() + + r = PaymentResponse.objects.create(api=p.url, + correlation_id = correlation, + timestamp = timestamp, + info = p.raw_response, + transaction=transaction) + + if p.success() and not p.error(): + logger.info("Cancel Transaction " + str(transaction.id) + " Completed") + return True + + else: + transaction.error = p.error_string() + transaction.save() + logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string()) + return False + else: - transaction.error = p.error_string() + + # if no explicit preapproval required, we just have to mark the transaction as cancelled. + transaction.status = TRANSACTION_STATUS_CANCELED transaction.save() - logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string()) - return False + return True def authorize(self, transaction, expiry= None, return_url=None, paymentReason="unglue.it Pledge", modification=False): ''' @@ -551,9 +554,9 @@ class PaymentManager( object ): ''' - if host==None: - #TODO send user to select a payment processor - pass + if transaction.host == PAYMENT_HOST_NONE: + #TODO send user to select a payment processor -- for now, set to a system setting + transaction.host = settings.PAYMENT_PROCESSOR # 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. @@ -561,11 +564,10 @@ class PaymentManager( object ): if return_url is None: return_path = "{0}?{1}".format(reverse('pledge_complete'), - urllib.urlencode({'tid':t.id})) + urllib.urlencode({'tid':transaction.id})) return_url = urlparse.urljoin(settings.BASE_URL, return_path) - method = getattr(t.get_payment_class(), "Preapproval") - p = method(transaction, transaction.max_amount, expiry, return_url=return_url, paymentReason=paymentReason) + p = transaction.get_payment_class().Preapproval(transaction, transaction.amount, expiry, return_url=return_url, paymentReason=paymentReason) # Create a response for this envelope = p.envelope() @@ -581,9 +583,15 @@ class PaymentManager( object ): transaction.preapproval_key = p.key() transaction.save() + # it make sense for the payment processor library to calculate next_url when + # user is redirected there. But if no redirection is required, send user + # straight on to the return_url url = p.next_url() + + if url is None: + url = return_url - logger.info("Authorize Success: " + url) + logger.info("Authorize Success: " + url if url is not None else '') # modification and initial pledge use different notification templates -- # decide which to send @@ -610,7 +618,7 @@ class PaymentManager( object ): logger.info("Authorize Error: " + p.error_string()) return transaction, None - def process_transaction(self, currency, amount, host=None, campaign=None, user=None, + def process_transaction(self, currency, amount, host=PAYMENT_HOST_NONE, campaign=None, user=None, return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None, modification=False): ''' @@ -634,7 +642,8 @@ class PaymentManager( object ): # set the expiry date based on the campaign deadline expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) - t = Transaction.create(amount=0, + t = Transaction.create(amount=0, + host = host, max_amount=amount, currency=currency, campaign=campaign, @@ -718,6 +727,16 @@ class PaymentManager( object ): return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed ''' + logger.info("transaction.id: {0}, amount:{1}".format(transaction.id, amount)) + + # if expiry is None, use the existing value + if expiry is None: + expiry = transaction.date_expired + + # does this transaction explicity require preapprovals? + + requires_explicit_preapprovals = transaction.get_payment_class().requires_explicit_preapprovals + if transaction.type != PAYMENT_TYPE_AUTHORIZATION: logger.info("Error, attempt to modify an invalid transaction type") return False, None @@ -757,7 +776,7 @@ class PaymentManager( object ): credit.CancelPreapproval(transaction) return t, reverse('fund_pledge', args=[t.id]) - elif amount > transaction.max_amount or expiry != transaction.date_expired: + elif requires_explicit_preapprovals and (amount > transaction.max_amount or expiry != transaction.date_expired): # set the expiry date based on the campaign deadline expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) @@ -800,7 +819,7 @@ class PaymentManager( object ): # corresponding notification to the user? that would go here. return False, None - elif amount <= transaction.max_amount: + elif (requires_explicit_preapprovals and amount <= transaction.max_amount) or (not requires_explicit_preapprovals): # Update transaction but leave the preapproval alone transaction.amount = amount transaction.set_pledge_extra(pledge_extra) @@ -833,8 +852,7 @@ class PaymentManager( object ): logger.info("Refund Transaction failed, invalid transaction status") return False - method = getattr(transaction.get_payment_class(), "RefundPayment") - p = method(transaction) + p = transaction.get_payment_class().RefundPayment(transaction) # Create a response for this envelope = p.envelope() @@ -892,8 +910,7 @@ class PaymentManager( object ): t.date_payment=now() t.execution=EXECUTE_TYPE_CHAINED_INSTANT t.type=PAYMENT_TYPE_INSTANT - method = getattr(t.get_payment_class(), "Pay") - p = method(t,return_url=return_url, nevermind_url=nevermind_url) + p = t.get_payment_class().Pay(t,return_url=return_url, nevermind_url=nevermind_url) # Create a response for this envelope = p.envelope() @@ -921,5 +938,13 @@ class PaymentManager( object ): t.save() logger.info("Pledge Error: %s" % p.error_string()) return t, None + + def make_account(self, user, token, host): + + """delegate to a specific payment module the task of creating a payment account""" + mod = __import__("regluit.payment." + host, fromlist=[host]) + return mod.Processor().make_account(user, token) + + diff --git a/payment/models.py b/payment/models.py index 1f5d5a6e..e8310e37 100644 --- a/payment/models.py +++ b/payment/models.py @@ -1,7 +1,7 @@ from django.db import models from django.contrib.auth.models import User from django.conf import settings -from regluit.core.models import Campaign, Wishlist, Premium, PledgeExtra + from regluit.payment.parameters import * from regluit.payment.signals import credit_balance_added, pledge_created from regluit.utils.localdatetime import now @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) # c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year # promising fields - + class Transaction(models.Model): # type e.g., PAYMENT_TYPE_INSTANT or PAYMENT_TYPE_AUTHORIZATION -- defined in parameters.py @@ -78,8 +78,8 @@ class Transaction(models.Model): # associated User, Campaign, and Premium for this Transaction user = models.ForeignKey(User, null=True) - campaign = models.ForeignKey(Campaign, null=True) - premium = models.ForeignKey(Premium, null=True) + campaign = models.ForeignKey('core.Campaign', null=True) + premium = models.ForeignKey('core.Premium', null=True) # how to acknowledge the user on the supporter page of the campaign ebook ack_name = models.CharField(max_length=64, null=True) @@ -109,13 +109,13 @@ class Transaction(models.Model): def get_payment_class(self): ''' - Returns the specific payment module that implements this transaction + Returns the specific payment processor 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 + return mod.Processor() def set_credit_approved(self, amount): self.amount=amount @@ -137,10 +137,12 @@ class Transaction(models.Model): 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) + class pe: + premium=self.premium + anonymous=self.anonymous + ack_name=self.ack_name + ack_dedication=self.ack_dedication + return pe @classmethod def create(cls,amount=0.00, host=PAYMENT_HOST_NONE, max_amount=0.00, currency='USD', diff --git a/payment/parameters.py b/payment/parameters.py index 0d408751..027bb7f6 100644 --- a/payment/parameters.py +++ b/payment/parameters.py @@ -5,7 +5,7 @@ PAYMENT_TYPE_AUTHORIZATION = 2 PAYMENT_HOST_NONE = "none" PAYMENT_HOST_PAYPAL = "paypal" PAYMENT_HOST_AMAZON = "amazon" -PAYMENT_HOST_STRIPE = "stripe" +PAYMENT_HOST_STRIPE = "stripelib" PAYMENT_HOST_TEST = "test" PAYMENT_HOST_CREDIT = "credit" diff --git a/payment/stripelib.py b/payment/stripelib.py index 8e6fa9f0..87050e7a 100644 --- a/payment/stripelib.py +++ b/payment/stripelib.py @@ -1,11 +1,25 @@ # https://github.com/stripe/stripe-python # https://stripe.com/docs/api?lang=python#top -from datetime import datetime +import logging +from datetime import datetime, timedelta from pytz import utc +from django.conf import settings + +from regluit.payment.models import Account +from regluit.payment.parameters import PAYMENT_HOST_STRIPE +from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, PAYMENT_TYPE_AUTHORIZATION, TRANSACTION_STATUS_CANCELED +from regluit.payment import baseprocessor +from regluit.utils.localdatetime import now, zuluformat + import stripe +logger = logging.getLogger(__name__) + +class StripeError(Exception): + pass + try: import unittest from unittest import TestCase @@ -25,15 +39,11 @@ 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 @@ -149,14 +159,14 @@ class StripeClient(object): 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 + # customer.id 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, + customer=customer, card=card, description=description ) @@ -217,7 +227,7 @@ class PledgeScenarioTest(TestCase): 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") + charge = self._sc.create_charge(10, customer=self._good_cust.id, description="$10 for good cust") self.assertEqual(type(charge.id), str) # print out all the pieces of Customer and Charge objects @@ -232,7 +242,7 @@ class PledgeScenarioTest(TestCase): 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") + customer = self._cust_bad_card.id, description="$10 for bad cust") @classmethod @@ -251,6 +261,158 @@ class PledgeScenarioTest(TestCase): 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'])] +class StripePaymentRequest(baseprocessor.BasePaymentRequest): + """so far there is no need to have a separate class here""" + pass + +class Processor(baseprocessor.Processor): + + def make_account(self, user, token): + """returns a payment.models.Account based on stripe token and user""" + + sc = StripeClient() + + # create customer and charge id and then charge the customer + customer = sc.create_customer(card=token, description=user.username, + email=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 = user + ) + + account.save() + + return account + + class Pay(StripePaymentRequest, baseprocessor.Processor.Pay): + pass + + class Preapproval(StripePaymentRequest, baseprocessor.Processor.Preapproval): + + 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 + + self.transaction = transaction + + now_val = now() + if expiry is None: + expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) + transaction.date_authorized = now_val + transaction.date_expired = expiry + + sc = 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 transaction.max_amount get set? -- coming from /pledge/xxx/ -> manager.process_transaction + # max_amount is set -- but I don't think we need it for stripe + + # ASSUMPTION: a user has any given moment one and only one active payment Account + + if transaction.user.account_set.filter(date_deactivated__isnull=True).count() > 1: + logger.warning("user {0} has more than one active payment account".format(transaction.user)) + elif transaction.user.account_set.filter(date_deactivated__isnull=True).count() == 0: + logger.warning("user {0} has no active payment account".format(transaction.user)) + raise StripeError("user {0} has no active payment account".format(transaction.user)) + + account = transaction.user.account_set.filter(date_deactivated__isnull=True)[0] + logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id)) + + # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE + # should approved be set to False and wait for a webhook? + transaction.approved = True + transaction.type = PAYMENT_TYPE_AUTHORIZATION + transaction.host = PAYMENT_HOST_STRIPE + transaction.status = TRANSACTION_STATUS_ACTIVE + + transaction.preapproval_key = account.account_id + + transaction.currency = 'USD' + transaction.amount = amount + + transaction.save() + + def key(self): + return self.transaction.preapproval_key + + def next_url(self): + """return None because no redirection to stripe is required""" + return None + + + class Execute(StripePaymentRequest): + + ''' + 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 + + # execute transaction + assert transaction.host == PAYMENT_HOST_STRIPE + + sc = StripeClient() + + # look at transaction.preapproval_key + # is it a customer or a token? + + # BUGBUG: replace description with somethin more useful + if transaction.preapproval_key.startswith('cus_'): + charge = sc.create_charge(transaction.amount, customer=transaction.preapproval_key, description="${0} for test / retain cc".format(transaction.amount)) + elif transaction.preapproval_key.startswith('tok_'): + charge = sc.create_charge(transaction.amount, card=transaction.preapproval_key, description="${0} for test / cc not retained".format(transaction.amount)) + + transaction.status = TRANSACTION_STATUS_COMPLETE + transaction.pay_key = charge.id + transaction.date_payment = now() + transaction.save() + + self.charge = charge + + 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 PreapprovalDetails(StripePaymentRequest): + ''' + 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): + + self.transaction = transaction + self.status = self.transaction.status + if self.status == TRANSACTION_STATUS_CANCELED: + self.approved = False + else: + self.approved = True + + # 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 + def suite(): testcases = [PledgeScenarioTest] diff --git a/payment/templates/stripe.html b/payment/templates/stripe.html index 83e7962f..488a7585 100644 --- a/payment/templates/stripe.html +++ b/payment/templates/stripe.html @@ -6,33 +6,80 @@ {% block extra_extra_head %} - - + + -{% endblock %} - -{% block doccontent %} -Stripe Test!: - - -
- {% csrf_token %} - - -
+ +{% endblock %} + +{% block doccontent %} +Stripe Test: + + +
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + + / + +
+ +
{% endblock %} diff --git a/payment/views.py b/payment/views.py index c0950859..35397d07 100644 --- a/payment/views.py +++ b/payment/views.py @@ -3,7 +3,6 @@ from regluit.payment.models import Transaction from regluit.core.models import Campaign, Wishlist from regluit.payment.stripelib import STRIPE_PK - from regluit.payment.forms import StripePledgeForm from django.conf import settings @@ -21,8 +20,7 @@ from django.views.generic.edit import FormView from django.views.generic.base import TemplateView from unittest import TestResult - - +from regluit.payment.tests import PledgeTest, AuthorizeTest import uuid from decimal import Decimal as D @@ -121,8 +119,12 @@ def testAuthorize(request): receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00}, {'email': TEST_RECEIVERS[1], 'amount':10.00}] - campaign = Campaign.objects.get(id=int(campaign_id)) - t, url = p.authorize(Transaction.objects.create(currency='USD', max_amount=amount, campaign=campaign, user=None), return_url=None) + if campaign_id: + campaign = Campaign.objects.get(id=int(campaign_id)) + t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, amount, campaign=campaign, return_url=None, list=None, user=None) + + else: + t, url = p.authorize('USD', TARGET_TYPE_NONE, amount, campaign=None, return_url=None, list=None, user=None) if url: logger.info("testAuthorize: " + url) @@ -253,9 +255,12 @@ def testPledge(request): else: receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}] - campaign = Campaign.objects.get(id=int(campaign_id)) - t, url = p.pledge('USD', receiver_list, campaign=campaign, list=None, user=user, return_url=None) + 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) + else: + t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=user, return_url=None) if url: logger.info("testPledge: " + url) @@ -266,6 +271,33 @@ def testPledge(request): logger.info("testPledge: Error " + str(t.error)) return HttpResponse(response) +def runTests(request): + + try: + # Setup the test environement. We need to run these tests on a live server + # so our code can receive IPN notifications from paypal + setup_test_environment() + result = TestResult() + + # Run the authorize test + test = AuthorizeTest('test_authorize') + test.run(result) + + # Run the pledge test + test = PledgeTest('test_pledge_single_receiver') + test.run(result) + + # Run the pledge failure test + test = PledgeTest('test_pledge_too_much') + test.run(result) + + output = "Tests Run: " + str(result.testsRun) + str(result.errors) + str(result.failures) + logger.info(output) + + return HttpResponse(output) + + except: + traceback.print_exc() @csrf_exempt def handleIPN(request, module): @@ -278,6 +310,11 @@ def handleIPN(request, module): return HttpResponse("ipn") +def paymentcomplete(request): + # pick up all get and post parameters and display + output = "payment complete" + output += request.method + "\n" + str(request.REQUEST.items()) + return HttpResponse(output) def checkStatus(request): # Check the status of all PAY transactions and flag any errors @@ -305,11 +342,7 @@ class StripeView(FormView): return context def form_valid(self, form): - stripe_token = form.cleaned_data["stripe_token"] + stripeToken = form.cleaned_data["stripeToken"] # e.g., tok_0C0k4jG5B2Oxox # - return HttpResponse("stripe_token: {0}".format(stripe_token)) - - - - + return HttpResponse("stripeToken: {0}".format(stripeToken)) diff --git a/requirements_versioned.pip b/requirements_versioned.pip index c4e36f49..84e806f1 100644 --- a/requirements_versioned.pip +++ b/requirements_versioned.pip @@ -46,6 +46,7 @@ requests==0.14.0 selenium==2.25.0 six==1.2.0 ssh==1.7.14 +stevedore==0.4 stripe==1.7.4 virtualenv==1.4.9 virtualenvwrapper==3.6 diff --git a/settings/common.py b/settings/common.py index 6504d44f..b0820fc9 100644 --- a/settings/common.py +++ b/settings/common.py @@ -278,7 +278,7 @@ 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 # amazon or paypal for now. -PAYMENT_PROCESSOR = 'test' +PAYMENT_PROCESSOR = 'stripelib' # a SECRET_KEY to be used for encrypting values in core.models.Key -- you should store in settings/local.py SECRET_KEY = '' diff --git a/test/campaign_starter.sql b/test/campaign_starter.sql index c0b7e342..0895b54a 100644 --- a/test/campaign_starter.sql +++ b/test/campaign_starter.sql @@ -582,6 +582,19 @@ INSERT INTO `core_identifier` VALUES (1,'goog','wtPxGztYx-UC',1,1),(2,'isbn','97 /*!40000 ALTER TABLE `core_identifier` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Create empty core_key +-- +DROP TABLE IF EXISTS `core_key`; +CREATE TABLE `core_key` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `encrypted_value` longtext, + PRIMARY KEY (`id`), + UNIQUE KEY `core_key_name_2fdc7c2d_uniq` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + -- -- Table structure for table `core_premium` -- diff --git a/test/campaigntest.py b/test/campaigntest.py index 7418602f..48b04290 100644 --- a/test/campaigntest.py +++ b/test/campaigntest.py @@ -1,7 +1,6 @@ from regluit.core import models from regluit.payment.models import Transaction, PaymentResponse, Receiver from regluit.payment.manager import PaymentManager -from regluit.payment.paypal import IPN_PREAPPROVAL_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_COMPLETED import django from django.conf import settings @@ -14,9 +13,6 @@ import unittest, time, re import logging import os -# PayPal developer sandbox -from regluit.payment.tests import loginSandbox, paySandbox, payAmazonSandbox - def setup_selenium(): # Set the display window for our xvfb os.environ['DISPLAY'] = ':99' @@ -154,15 +150,9 @@ def recipient_status(clist): # res = [pm.finish_campaign(c) for c in campaigns_incomplete()] - -def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=True, backend='amazon', browser='firefox'): - """ - programatically fire up selenium to make a Pledge - do_local should be True only if you are running support_campaign on db tied to LIVE_SERVER_TEST_URL - """ - +def test_relaunch(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=True, backend='amazon', browser='chrome'): django.db.transaction.enter_transaction_management() - + UNGLUE_IT_URL = unglue_it_url USER = settings.UNGLUEIT_TEST_USER PASSWORD = settings.UNGLUEIT_TEST_PASSWORD @@ -179,17 +169,12 @@ def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=Tru else: sel = webdriver.Firefox() - time.sleep(10) + time.sleep(5) - # find a campaign to pledge to - if backend == 'paypal': - loginSandbox(sel) - time.sleep(2) - print "now opening unglue.it" #sel.get("http://www.google.com") - sel.get(UNGLUE_IT_URL) + sel.get(UNGLUE_IT_URL) # long wait because sel is slow after PayPal sign_in_link = WebDriverWait(sel, 100).until(lambda d : d.find_element_by_xpath("//span[contains(text(),'Sign In')]/..")) @@ -199,7 +184,7 @@ def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=Tru input_username = WebDriverWait(sel,20).until(lambda d : d.find_element_by_css_selector("input#id_username")) input_username.send_keys(USER) sel.find_element_by_css_selector("input#id_password").send_keys(PASSWORD) - sel.find_element_by_css_selector("input[value*='sign in']").click() + sel.find_element_by_css_selector("input[value*='sign in']").click() # click on biggest campaign list # I have no idea why selenium thinks a is not displayed....so that's why I'm going up one element. @@ -225,62 +210,49 @@ def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=Tru print "yes: Error in just hitting pledge button as expected" else: print "ooops: there should be an error message when pledge button hit" - - # fill out a premium -- the first one for now - premium_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector('input[type="radio"][value="1"]')) - premium_button.click() print "making $10 pledge" # now we have to replace the current preapproval amount with 10 sel.execute_script("""document.getElementById("id_preapproval_amount").value="10";""") - # must also pick a premium level -- otherwise there will not be a pledge_button -- let's pick the first premium ($1) - premium_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector('input[type="radio"][value="1"]')) - premium_button.click() + radio_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='150']")) + radio_button.click() - pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Pledge']")) - pledge_button.click() + support_button = WebDriverWait(sel,10).until(lambda d: d.find_element_by_css_selector("input[value*='Pledge Now']")) + support_button.click() - # grab the URL where sel is now? - - if backend == 'paypal': - print "Now trying to pay PayPal", sel.current_url - paySandbox(None, sel, sel.current_url, authorize=True, already_at_url=True, sleep_time=5) - elif backend == 'amazon': - payAmazonSandbox(sel) + # now fill out the credit card + sel.execute_script("""document.getElementById("card_Number").value="4242424242424242";""") + sel.execute_script("""document.getElementById("card_ExpiryMonth").value="01";""") + sel.execute_script("""document.getElementById("card_ExpiryYear").value="14";""") + sel.execute_script("""document.getElementById("card_CVC").value="123";""") + + verify_cc_button = WebDriverWait(sel,10).until(lambda d: d.find_element_by_css_selector("input[value*='Verify Credit Card']")) + verify_cc_button.click() + + # verify that we are at pledge_complete + # sleep a bit to give enough time for redirecto pledge_complete to finish + + time.sleep(3) + # should be back on a pledge complete page print sel.current_url, re.search(r"/pledge/complete",sel.current_url) - time.sleep(2) - django.db.transaction.commit() - - # time out to simulate an IPN -- update all the transactions - if do_local: - django.db.transaction.enter_transaction_management() - pm = PaymentManager() - print pm.checkStatus() - transaction0 = Transaction.objects.all()[0] - print "transaction amount:{0}, transaction premium:{1}".format(transaction0.amount, transaction0.premium.id) - django.db.transaction.commit() - - - django.db.transaction.enter_transaction_management() - - # I have no idea what the a[href*="/work/"] is not displayed....so that's why I'm going up one element. work_url = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector('p > a[href*="/work/"]')) work_url.click() - + # change_pledge print "clicking Modify Pledge button" change_pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Modify Pledge']")) change_pledge_button.click() # enter a new pledge, which is less than the previous amount and therefore doesn't require a new PayPal transaction - print "changing pledge to $5 -- should not need to go to PayPal" - preapproval_amount_input = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input#id_preapproval_amount")) - preapproval_amount_input.clear() # get rid of existing pledge - preapproval_amount_input.send_keys("5") + print "changing pledge to $5 -- should not need to go to Stripe" + sel.execute_script("""document.getElementById("id_preapproval_amount").value="5";""") + radio_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='150']")) + radio_button.click() + pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Modify Pledge']")) pledge_button.click() @@ -289,31 +261,16 @@ def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=Tru work_url.click() change_pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Modify Pledge']")) change_pledge_button.click() - - # enter a new pledge, which is more than the previous amount and therefore requires a new PayPal transaction - preapproval_amount_input = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input#id_preapproval_amount")) - preapproval_amount_input.clear() # get rid of existing pledge - preapproval_amount_input.send_keys("25") + + # modify pledge to $25 + sel.execute_script("""document.getElementById("id_preapproval_amount").value="25";""") + radio_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='150']")) + radio_button.click() + pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Modify Pledge']")) pledge_button.click() - if backend == 'paypal': - paySandbox(None, sel, sel.current_url, authorize=True, already_at_url=True, sleep_time=5) - elif backend == 'amazon': - payAmazonSandbox(sel) - - # wait a bit to allow PayPal sandbox to be update the status of the Transaction - time.sleep(10) - django.db.transaction.commit() - - # time out to simulate an IPN -- update all the transactions - if do_local: - django.db.transaction.enter_transaction_management() - pm = PaymentManager() - print pm.checkStatus() - django.db.transaction.commit() - - django.db.transaction.enter_transaction_management() + # now cancel transaction # now go back to the work page, hit modify pledge, and then the cancel link work_url = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector('p > a[href*="/work/"]')) work_url.click() @@ -324,26 +281,27 @@ def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=Tru # hit the confirm cancellation button cancel_pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Confirm Pledge Cancellation']")) - cancel_pledge_button.click() + cancel_pledge_button.click() + time.sleep(10) django.db.transaction.commit() - - # Why is the status of the new transaction not being updated? - # force a db lookup -- see whether there are 1 or 2 transactions - # they should both be cancelled - if do_local: - transactions = list(Transaction.objects.all()) - print "number of transactions", Transaction.objects.count() - - print "transactions before pm.checkStatus" - print [(t.id, t.type, t.preapproval_key, t.status, t.premium, t.amount) for t in Transaction.objects.all()] - - print "checkStatus:", pm.checkStatus(transactions=transactions) - - yield sel - #sel.quit() + + + # now use the transaction manager to make the charge + w = models.Work.objects.get(id=48) + c = w.campaigns.all()[0] + pm = PaymentManager() + result = pm.execute_campaign(c) + + # should have a Complete transaction + print result + + yield sel + + + def successful_campaign_signal(): """fire off a success_campaign signal and send notifications"""