Merge branch 'relaunch_ry'
commit
410f1cb9ec
|
@ -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() )
|
||||
|
|
|
@ -8,8 +8,53 @@
|
|||
<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>
|
||||
<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
Stripe.setPublishableKey('{{STRIPE_PK}}');
|
||||
</script>
|
||||
|
||||
<script type="application/x-javascript">
|
||||
|
||||
var $j = jQuery.noConflict();
|
||||
|
||||
|
||||
function stripeResponseHandler(status, response) {
|
||||
if (response.error) {
|
||||
// re-enable the submit button
|
||||
$j('.submit-button').removeAttr("disabled");
|
||||
// show the errors on the form
|
||||
$j(".payment-errors").html(response.error.message);
|
||||
} else {
|
||||
var form$ = $j("#payment-form");
|
||||
// token contains id, last4, and card type
|
||||
var token = response['id'];
|
||||
|
||||
// insert the token into the form so it gets submitted to the server
|
||||
form$.append("<input type='hidden' name='stripe_token' value='" + token + "' />");
|
||||
// and submit
|
||||
form$.get(0).submit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$j().ready(function() {
|
||||
$j("#payment-form").submit(function(event) {
|
||||
// disable the submit button to prevent repeated clicks
|
||||
$j('.submit-button').attr("disabled", "disabled");
|
||||
|
||||
Stripe.createToken({
|
||||
number: $j('.card-number').val(),
|
||||
cvc: $j('.card-cvc').val(),
|
||||
exp_month: $j('.card-expiry-month').val(),
|
||||
exp_year: $j('.card-expiry-year').val()
|
||||
}, stripeResponseHandler);
|
||||
|
||||
// prevent the form from submitting with the default action
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
@ -55,30 +100,31 @@
|
|||
</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>
|
||||
<span class="payment-errors"></span>
|
||||
<form action="" method="POST" id="payment-form">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{{ form.as_p }}
|
||||
<div class="form-row">
|
||||
<label>Card Number</label>
|
||||
<input id="card_Number" type="text" size="20" autocomplete="off" class="card-number"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>CVC</label>
|
||||
<input id="card_CVC" type="text" size="4" autocomplete="off" class="card-cvc"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Expiration (MM/YYYY)</label>
|
||||
<input id="card_ExpiryMonth" type="text" size="2" class="card-expiry-month"/>
|
||||
<span> / </span>
|
||||
<input id="card_ExpiryYear" type="text" size="4" class="card-expiry-year"/>
|
||||
</div>
|
||||
<input id="cc_submit" type="submit" class="submit-button" value="Verify Credit Card" />
|
||||
</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 %}
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -6,33 +6,80 @@
|
|||
{% 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>
|
||||
|
||||
<script type="text/javascript" src="https://js.stripe.com/v1/"></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="text/javascript">
|
||||
// this identifies your website in the createToken call below
|
||||
Stripe.setPublishableKey('{{STRIPE_PK}}');
|
||||
</script>
|
||||
|
||||
<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');
|
||||
|
||||
function stripeResponseHandler(status, response) {
|
||||
if (response.error) {
|
||||
// re-enable the submit button
|
||||
$j('.submit-button').removeAttr("disabled");
|
||||
// show the errors on the form
|
||||
$j(".payment-errors").html(response.error.message);
|
||||
} else {
|
||||
var form$ = $j("#payment-form");
|
||||
// token contains id, last4, and card type
|
||||
var token = response['id'];
|
||||
// insert the token into the form so it gets submitted to the server
|
||||
form$.append("<input type='hidden' name='stripe_token' value='" + token + "' />");
|
||||
// and submit
|
||||
form$.get(0).submit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$j().ready(function() {
|
||||
$j("#payment-form").submit(function(event) {
|
||||
// disable the submit button to prevent repeated clicks
|
||||
$j('.submit-button').attr("disabled", "disabled");
|
||||
|
||||
Stripe.createToken({
|
||||
number: $j('.card-number').val(),
|
||||
cvc: $j('.card-cvc').val(),
|
||||
exp_month: $j('.card-expiry-month').val(),
|
||||
exp_year: $j('.card-expiry-year').val()
|
||||
}, stripeResponseHandler);
|
||||
|
||||
// prevent the form from submitting with the default action
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
Stripe Test:
|
||||
|
||||
<span class="payment-errors"></span>
|
||||
<form action="" method="POST" id="payment-form">
|
||||
{% csrf_token %}
|
||||
<div class="form-row">
|
||||
<label>Card Number</label>
|
||||
<input type="text" size="20" autocomplete="off" class="card-number"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>CVC</label>
|
||||
<input type="text" size="4" autocomplete="off" class="card-cvc"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Expiration (MM/YYYY)</label>
|
||||
<input type="text" size="2" class="card-expiry-month"/>
|
||||
<span> / </span>
|
||||
<input type="text" size="4" class="card-expiry-year"/>
|
||||
</div>
|
||||
<button type="submit" class="submit-button">Submit Payment</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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`
|
||||
--
|
||||
|
|
|
@ -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"""
|
||||
|
|
Loading…
Reference in New Issue