Merge branch 'master' of github.com:Gluejar/regluit

pull/1/head
Andromeda Yelton 2012-05-04 10:05:54 -04:00
commit 4be0cab5a0
32 changed files with 3370 additions and 286 deletions

View File

@ -189,7 +189,7 @@ def update_edition(edition):
# update the edition
edition.title = title
edition.description = d.get('description')
# edition.description = d.get('description')
edition.publisher = d.get('publisher')
edition.publication_date = d.get('publishedDate', '')
edition.save()
@ -331,7 +331,7 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
# because this is a new google id, we have to create a new edition
e = models.Edition(work=work)
e.title = title
e.description = d.get('description')
#e.description = d.get('description')
e.publisher = d.get('publisher')
e.publication_date = d.get('publishedDate', '')
e.save()

View File

@ -28,9 +28,9 @@ class Command(BaseCommand):
campaign.paypal_receiver = settings.PAYPAL_TEST_RH_EMAIL
# random deadline between 5 days from now and 180 days from now
now = now()
campaign.deadline = random_date(now + timedelta(days=5),
now + timedelta(days=180))
_now = now()
campaign.deadline = random_date(_now + timedelta(days=5),
_now + timedelta(days=180))
# randomly activate some of the campaigns
coinflip = D(randint(0,10))

View File

@ -183,10 +183,15 @@ class Campaign(models.Model):
status = self.status
if status != 'INITIALIZED':
raise UnglueitError(_('Campaign needs to be initialized in order to be activated'))
try:
active_claim = self.work.claim.filter(status="active")[0]
except IndexError, e:
raise UnglueitError(_('Campaign needs to have an active claim in order to be activated'))
self.status= 'ACTIVE'
self.left = self.target
self.save()
active_claim = self.work.claim.filter(status="active")[0]
ungluers = self.work.wished_by()
notification.queue(ungluers, "wishlist_active", {'campaign':self, 'active_claim':active_claim}, True)
return self
@ -579,7 +584,7 @@ class WasWork(models.Model):
class Ebook(models.Model):
FORMAT_CHOICES = (('PDF','PDF'),( 'EPUB','EPUB'), ('HTML','HTML'), ('TEXT','TEXT'), ('MOBI','MOBI'))
FORMAT_CHOICES = (('pdf','PDF'),( 'epub','EPUB'), ('html','HTML'), ('text','TEXT'), ('mobi','MOBI'))
RIGHTS_CHOICES = (('PD-US', 'Public Domain, US'),
('CC BY-NC-ND','CC BY-NC-ND'),
('CC BY-ND','CC BY-ND'),

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA7ozGYIwEEXj10hAICsy+qdBHYbV95cs7rSR8ZG+6te2aPL1Pg0e5lUJkyMao0T//LfFNBRwg0wqWsb7b6yUvXmJAD3r5hjTCWlKJ/54APxInT/YMi08GVwSnwgVnib8zmjmfJ+6zFj5MsPTNPsdL/DWxheyZ0oHDxL0rpcL192sdgu4//5j0oyk+w/rrm+jUMNpuOOGPINMZxEd6+OMXr239ZLGSkWAavtMW1FKOXc/qrLHn03rNdz4cHZ1Gsx2++d8lk4jf8BzC0t3XXVuRZNu8lhlzCMW6sUEG6uACnXYlnmQg597fcVUFXmdQ4I79PGndjy35FwdbmiLLe+bnrw== raymond.yee@gmail.com

38
fabfile.py vendored
View File

@ -1,4 +1,5 @@
from fabric.api import run, local, env, cd
from regluit.sysadmin import aws
# allow us to use our ssh config files (e.g., ~/.ssh/config)
env.use_ssh_config = True
@ -18,8 +19,8 @@ def update_prod():
with cd("/opt/regluit"):
run("./deploy/update-prod")
def backup_db():
run("""TS=`date +"%Y-%m-%dT%H:%M:%S"`; /home/ubuntu/dump.sh | gzip > unglue.it.${TS}.sql.gz; scp ./unglue.it.${TS}.sql.gz b235656@hanjin.dreamhost.com: ; rm -f unglue.it.${TS}.sql.gz""")
def backup_db(name='unglue.it'):
run("""TS=`date +"%Y-%m-%dT%H:%M:%S"`; /home/ubuntu/dump.sh | gzip > {0}.${{TS}}.sql.gz; scp ./{0}.${{TS}}.sql.gz b235656@hanjin.dreamhost.com: ; rm -f {0}.${{TS}}.sql.gz""".format(name))
def get_dump():
"""Dump the current db on remote server and scp it over to local machine.
@ -29,6 +30,39 @@ def get_dump():
run("gzip -f unglue.it.sql")
local("scp web1:/home/ubuntu/unglue.it.sql.gz .")
local("gunzip -f unglue.it.sql.gz")
def copy_dump_to_ry_dev():
"""Dump the current db on remote server and scp it over to ry-dev.
Note: web1 has been hardcoded here to represent the name of the unglue.it server
"""
run("./dump.sh > unglue.it.sql ")
run("scp unglue.it.sql ry-dev.dyndns.org:")
def build_prod_instance(ami_id='ami-a29943cb'):
"""Build a new instance to serve as server instance for unglue.it"""
# http://my.safaribooksonline.com/book/-/9781449308100/2dot-ec2-recipes/id2529379
# default ami-a29943cb' is Ubuntu 12.04 Precise EBS boot
def ecdsa():
"""Calculate the host ECSDA host fingerprint http://bridge.grumpy-troll.org/2011/01/openssh.html """
run("""ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key.pub -l""")
def ssh_fingerprint():
"""display ssh fingerprint of /home/ubuntu/.ssh/id_rsa.pub on remote machine"""
run ("""ssh-keygen -l -f /home/ubuntu/.ssh/id_rsa.pub""")
def ssh_fingerprint2():
# http://stackoverflow.com/a/6682934/7782
import base64,hashlib
def lineToFingerprint(line):
key = base64.b64decode(line.strip().partition('ssh-rsa ')[2])
fp_plain = hashlib.md5(key).hexdigest()
return ':'.join(a+b for a,b in zip(fp_plain[::2], fp_plain[1::2]))
def public_key_from_private_key():
# ssh-keygen -y -f ~/.ssh/id_rsa
pass
def email_addresses():
"""list email addresses in unglue.it"""

View File

@ -49,7 +49,8 @@ from regluit.frontend.forms import EbookForm, CustomPremiumForm, EditManagersFor
from regluit.payment.manager import PaymentManager
from regluit.payment.models import Transaction
from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION, PAYMENT_TYPE_AUTHORIZATION
from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_NONE, IPN_PREAPPROVAL_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_COMPLETED, IPN_PREAPPROVAL_STATUS_CANCELED, IPN_TYPE_PREAPPROVAL
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE_PRIMARY, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE
from regluit.payment.paypal import Preapproval
from regluit.core import goodreads
from tastypie.models import ApiKey
from regluit.payment.models import Transaction
@ -142,7 +143,7 @@ def work(request, work_id, action='display'):
pledged = None
countdown = ""
if work.last_campaign_status() == 'ACTIVE':
if work.last_campaign_status == 'ACTIVE':
time_remaining = campaign.deadline - now()
if time_remaining.days:
countdown = "in %s days" % time_remaining.days
@ -543,11 +544,11 @@ class PledgeModifyView(FormView):
# which combination of campaign and transaction status required?
# Campaign must be ACTIVE
assert campaign.status == 'ACTIVE'
transactions = campaign.transactions().filter(user=user, status=IPN_PREAPPROVAL_STATUS_ACTIVE)
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
assert transactions.count() == 1
transaction = transactions[0]
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == IPN_PREAPPROVAL_STATUS_ACTIVE
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
except Exception, e:
raise e
@ -610,10 +611,10 @@ class PledgeModifyView(FormView):
except models.Premium.DoesNotExist, e:
premium = None
transactions = campaign.transactions().filter(user=user, status=IPN_PREAPPROVAL_STATUS_ACTIVE)
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
assert transactions.count() == 1
transaction = transactions[0]
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == IPN_PREAPPROVAL_STATUS_ACTIVE
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
p = PaymentManager(embedded=self.embedded)
status, url = p.modify_transaction(transaction=transaction, amount=preapproval_amount, premium=premium)
@ -929,22 +930,21 @@ def campaign_admin(request):
# pull out Campaigns with Transactions that are ACTIVE -- and hence can be executed
# Campaign.objects.filter(transaction__status='ACTIVE')
campaigns_with_active_transactions = models.Campaign.objects.filter(transaction__status=IPN_PREAPPROVAL_STATUS_ACTIVE)
campaigns_with_active_transactions = models.Campaign.objects.filter(transaction__status=TRANSACTION_STATUS_ACTIVE)
# pull out Campaigns with Transactions that are INCOMPLETE
campaigns_with_incomplete_transactions = models.Campaign.objects.filter(transaction__status=IPN_PAY_STATUS_INCOMPLETE)
campaigns_with_incomplete_transactions = models.Campaign.objects.filter(transaction__status=TRANSACTION_STATUS_INCOMPLETE)
# show all Campaigns with Transactions that are COMPLETED
campaigns_with_completed_transactions = models.Campaign.objects.filter(transaction__status=IPN_PAY_STATUS_COMPLETED)
campaigns_with_completed_transactions = models.Campaign.objects.filter(transaction__status=TRANSACTION_STATUS_COMPLETE_PRIMARY)
# show Campaigns with Transactions that are CANCELED
campaigns_with_canceled_transactions = models.Campaign.objects.filter(transaction__status=IPN_PREAPPROVAL_STATUS_CANCELED)
campaigns_with_canceled_transactions = models.Campaign.objects.filter(transaction__status=TRANSACTION_STATUS_CANCELED)
return (campaigns_with_active_transactions, campaigns_with_incomplete_transactions, campaigns_with_completed_transactions,
campaigns_with_canceled_transactions)
return (campaigns_with_active_transactions, campaigns_with_incomplete_transactions, campaigns_with_completed_transactions, campaigns_with_canceled_transactions)
form = CampaignAdminForm()
pm = PaymentManager()

787
payment/amazon.py Normal file
View File

@ -0,0 +1,787 @@
from regluit.payment.parameters import *
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.models import Transaction, PaymentResponse
from boto.fps.connection import FPSConnection
from django.http import HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden
from datetime import timedelta
from regluit.utils.localdatetime import now, zuluformat
from boto import handler
from boto.resultset import ResultSet
from boto.exception import FPSResponseError
import xml.sax
import traceback
import datetime
import logging
import urlparse
import time
import urllib
logger = logging.getLogger(__name__)
AMAZON_STATUS_SUCCESS_ABT = 'SA'
AMAZON_STATUS_SUCCESS_ACH = 'SB'
AMAZON_STATUS_SUCCESS_CREDIT = 'SC'
AMAZON_STATUS_ERROR = 'SE'
AMAZON_STATUS_ADBANDONED = 'A'
AMAZON_STATUS_EXCEPTION = 'CE'
AMAZON_STATUS_PAYMENT_MISMATCH = 'PE'
AMAZON_STATUS_INCOMPLETE = 'NP'
AMAZON_STATUS_NOT_REGISTERED = 'NM'
AMAZON_STATUS_CANCELED = 'Canceled'
AMAZON_STATUS_FAILURE = 'Failure'
AMAZON_STATUS_PENDING = 'Pending'
AMAZON_STATUS_RESERVED = 'Reserved'
AMAZON_STATUS_SUCCESS = 'Success'
AMAZON_IPN_STATUS_CANCELED = 'CANCELED'
AMAZON_IPN_STATUS_FAILURE = 'FAILURE'
AMAZON_IPN_STATUS_PENDING = 'PENDING'
AMAZON_IPN_STATUS_RESERVED = 'RESERVED'
AMAZON_IPN_STATUS_SUCCESS = 'SUCCESS'
AMAZON_NOTIFICATION_TYPE_STATUS = 'TransactionStatus'
AMAZON_NOTIFICATION_TYPE_CANCEL = 'TokenCancellation'
AMAZON_OPERATION_TYPE_PAY = 'PAY'
AMAZON_OPERATION_TYPE_REFUND = 'REFUND'
AMAZON_OPERATION_TYPE_CANCEL = 'CANCEL'
def ProcessIPN(request):
'''
IPN handler for amazon. Here is a litle background on amazon IPNS
http://docs.amazonwebservices.com/AmazonFPS/latest/FPSAdvancedGuide/APPNDX_IPN.html
notificationType: Can either be TransactionStatus of TokenCancellation
status: One of the defined IPN status codes
operation: The type of operation
callerReference: The reference to find the transaction
The IPN is called for the following cases:
A payment or reserve succeeds
A payment or reserve fails
A payment or reserve goes into a pending state
A reserved payment is settled successfully
A reserved payment is not settled successfully
A refund succeeds
A refund fails
A refund goes into a pending state
A payment is canceled
A reserve is canceled
A token is canceled successfully
'''
try:
logging.debug("Amazon IPN called")
uri = request.build_absolute_uri()
parsed_url = urlparse.urlparse(uri)
connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
# Check the validity of the IPN
resp = connection.verify_signature("%s://%s%s" %(parsed_url.scheme,
parsed_url.netloc,
parsed_url.path),
request.raw_post_data)
if not resp[0].VerificationStatus == "Success":
# Error, ignore this IPN
logging.error("Amazon IPN cannot be verified with post data: ")
logging.error(request.raw_post_data)
return HttpResponseForbidden()
logging.debug("Amazon IPN post data:")
logging.debug(request.POST)
reference = request.POST['callerReference']
type = request.POST['notificationType']
# In the case of cancelling a token, there is no transaction, so this info is not set
transactionId = request.POST.get('transactionId', None)
date = request.POST.get('transactionDate', None)
operation = request.POST.get('operation', None)
status = request.POST.get('transactionStatus', None)
logging.info("Received Amazon IPN with the following data:")
logging.info("type = %s" % type)
logging.info("operation = %s" % operation)
logging.info("reference = %s" % reference)
logging.info("status = %s" % status)
# We should always find the transaction by the token
transaction = Transaction.objects.get(secret=reference)
if type == AMAZON_NOTIFICATION_TYPE_STATUS:
# status update for the token, save the actual value
transaction.local_status = status
# Now map our local status to the global status codes
if operation == AMAZON_OPERATION_TYPE_PAY:
if status == AMAZON_IPN_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_COMPLETE_PRIMARY
elif status == AMAZON_IPN_STATUS_PENDING:
if transaction.status == TRANSACTION_STATUS_CREATED:
#
# Per the amazon documentation:
# If your IPN receiving service is down for some time, it is possible that our retry mechanism will deliver the IPNs out of order.
# If you receive an IPN for TransactionStatus (IPN), as SUCCESS or FAILURE or RESERVED,
# then after that time ignore any IPN that gives the PENDING status for the transaction
#
transaction.status = TRANSACTION_STATUS_PENDING
else:
transaction.status = TRANSACTION_STATUS_ERROR
elif operation == AMAZON_OPERATION_TYPE_REFUND:
if status == AMAZON_IPN_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_REFUNDED
elif status == AMAZON_IPN_STATUS_PENDING:
transaction.status = TRANSACTION_STATUS_PENDING
else:
transaction.status = TRANSACTION_STATUS_ERROR
elif operation == AMAZON_OPERATION_TYPE_CANCEL:
if status == AMAZON_IPN_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_COMPLETE_PRIMARY
else:
transaction.status = TRANSACTION_STATUS_ERROR
elif type == AMAZON_NOTIFICATION_TYPE_CANCEL:
#
# The cancel IPN does not have a transaction ID or transaction status, so make them up
#
transaction.local_status = AMAZON_IPN_STATUS_CANCELED
transaction.status = TRANSACTION_STATUS_CANCELED
status = AMAZON_IPN_STATUS_CANCELED
transaction.save()
#
# This is currently not done in paypal land, but log this IPN since the amazon IPN has good info
#
PaymentResponse.objects.create(api="IPN",
correlation_id = transactionId,
timestamp = date,
info = str(request.POST),
status=status,
transaction=transaction)
return HttpResponse("Complete")
except:
traceback.print_exc()
return HttpResponseForbidden()
def amazonPaymentReturn(request):
'''
This is the complete view called after the co-branded API completes. It is called whenever the user
approves a preapproval or a pledge. This URL is set via the PAY api.
'''
try:
# pick up all get and post parameters and display
output = "payment complete"
output += request.method + "\n" + str(request.REQUEST.items())
signature = request.GET['signature']
reference = request.GET['callerReference']
token = request.GET['tokenID']
status = request.GET['status']
# BUGUBG - Should we verify the signature here?
#
# Find the transaction by reference, there should only be one
# We will catch the exception if it does not exist
#
transaction = Transaction.objects.get(secret=reference)
logging.info("Amazon Co-branded Return URL called for transaction id: %d" % transaction.id)
logging.info(request.GET)
#
# BUGBUG, for now lets map amazon status code to paypal, just to keep things uninform
#
if transaction.type == PAYMENT_TYPE_INSTANT:
# Instant payments need to be executed now
# Log the authorize transaction
r = PaymentResponse.objects.create(api="Authorize",
correlation_id = "None",
timestamp = str(datetime.datetime.now()),
info = str(request.GET),
status=status,
transaction=transaction)
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
# The above status code are unique to the return URL and are different than the pay API codes
# Store the token, we need this for the IPN.
transaction.pay_key = token
#
# BUGBUG, need to handle multiple recipients
# Send the pay request now to ourselves
#
e = Execute(transaction=transaction)
if e.success() and not e.error():
# Success case, save the ID. Our IPN will update the status
print "Amazon Execute returned succesfully"
else:
logging.error("Amazon payment execution failed: ")
logging.error(e.envelope())
transaction.status = TRANSACTION_STATUS_ERROR
# Log the pay transaction
r = PaymentResponse.objects.create(api="Pay",
correlation_id = e.correlation_id(),
timestamp = e.timestamp(),
info = e.envelope(),
status = e.status,
transaction=transaction)
else:
# We may never see an IPN, set the status here
logging.error("Amazon payment authorization failed: ")
logging.error(request.GET)
transaction.status = AMAZON_STATUS_FAILURE
elif transaction.type == PAYMENT_TYPE_AUTHORIZATION:
#
# Future payments, we only need to store the token. The authorization was requested with the default expiration
# date set in our settings. When we are ready, we can call execute on this
#
transaction.local_status = status
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
# The above status code are unique to the return URL and are different than the pay API codes
transaction.status = TRANSACTION_STATUS_ACTIVE
transaction.approved = True
transaction.pay_key = token
else:
# We may never see an IPN, set the status here
transaction.status = TRANSACTION_STATUS_ERROR
# Log the trasaction
r = PaymentResponse.objects.create(api="Authorize",
correlation_id = "None",
timestamp = str(datetime.datetime.now()),
info = str(request.GET),
status = status,
transaction=transaction)
transaction.save()
# Redirect to our pledge success URL
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
except:
logging.error("Amazon co-branded return-url FAILED with exception:")
traceback.print_exc()
cancel_path = "{0}?{1}".format(reverse('pledge_cancel'),
urllib.urlencode({'tid':transaction.id}))
cancel_url = urlparse.urljoin(settings.BASE_URL, cancel_path)
return HttpResponseRedirect(cancel_url)
class AmazonRequest:
'''
Handles common information that is processed from the response envelope of the amazon request.
'''
# Global values for the class
response = None
raw_response = None
errorMessage = None
status = None
url = None
def ack( self ):
return None
def success(self):
print "CALLING SUCCESS"
if self.errorMessage:
return False
else:
return True
def error(self):
if self.errorMessage:
return True
else:
return False
def error_data(self):
return None
def error_id(self):
return None
def error_string(self):
return self.errorMessage
def envelope(self):
# The envelope is used to store info about this request
if self.response:
return str(self.response)
else:
return None
def correlation_id(self):
# The correlation ID is unique to each API call
if self.response:
return self.response.TransactionId
else:
return None
def timestamp(self):
return str(datetime.datetime.now())
class Pay( AmazonRequest ):
'''
The pay function generates a redirect URL to approve the transaction
'''
def __init__( self, transaction, return_url=None, cancel_url=None, amount=None):
try:
logging.debug("Amazon PAY operation for transaction ID %d" % transaction.id)
# Replace our return URL with a redirect through our internal URL
self.original_return_url = return_url
return_url = settings.BASE_URL + reverse('AmazonPaymentReturn')
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
receiver_list = []
receivers = transaction.receiver_set.all()
if not amount:
amount = 0
for r in receivers:
amount += r.amount
logger.info(receiver_list)
# Data fields for amazon
expiry = now() + timedelta( days=settings.PREAPPROVAL_PERIOD )
data = {
'amountType':'Maximum', # The transaction amount is the maximum amount
'callerReference': transaction.secret,
'currencyCode': 'USD',
'globalAmountLimit': str(amount),
'validityExpiry': str(int(time.mktime(expiry.timetuple()))), # use the preapproval date by default
}
self.url = self.connection.make_url(return_url, "Test Payment", "MultiUse", str(amount), **data)
logging.debug("Amazon PAY redirect url was: %s" % self.url)
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon PAY api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon PAY FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
def api(self):
return "Amazon Co-branded PAY request"
def exec_status( self ):
return None
def amount( self ):
return None
def key( self ):
return None
def next_url( self ):
return self.url
def embedded_url(self):
return None
class Preapproval(Pay):
def __init__( self, transaction, amount, expiry=None, return_url=None, cancel_url=None):
# 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, cancel_url=cancel_url, amount=amount)
class Execute(AmazonRequest):
'''
The Execute function sends an existing token(generated via the URL from the pay operation), and collects
the money.
'''
def __init__(self, transaction=None):
try:
logging.debug("Amazon EXECUTE action for transaction id: %d" % transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
# BUGBUG, handle multiple receivers! For now we just send the money to ourselves
self.raw_response = self.connection.pay(transaction.amount,
transaction.pay_key,
recipientTokenId=None,
callerReference=transaction.secret,
senderReference=None,
recipientReference=None,
senderDescription=None,
recipientDescription=None,
callerDescription=None,
metadata=None,
transactionDate=None,
reserve=False)
#
# BUGBUG:
# The boto FPS library throws an exception if an error is generated, we need to do a better
# job of reporting the error when this occurs
#
self.response = self.raw_response[0]
logging.debug("Amazon EXECUTE response for transaction id: %d" % transaction.id)
logging.debug(str(self.response))
self.status = self.response.TransactionStatus
#
# For amazon, the transactionID is per transaction, not per receiver. For now we will store it in the preapproval key field
# so we can use it to refund or get status later
#
transaction.preapproval_key = self.response.TransactionId
logging.debug("Amazon EXECUTE API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon EXECUTE api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon EXECUTE FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
def api(self):
return "Amazon API Pay"
def key(self):
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key
return self.transaction.pay_key
class Finish(AmazonRequest):
'''
The Finish function handles the secondary receiver in a chained payment. Currently not implemented
for amazon
'''
def __init__(self, transaction):
try:
print "Finish"
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class PaymentDetails(AmazonRequest):
def __init__(self, transaction=None):
try:
logging.debug("Amazon PAYMENTDETAILS API for transaction id: %d" % transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
if not transaction.preapproval_key:
# This is where we store the transaction ID
self.errorMessage = "No Valid Transaction ID"
return
#
# We need to reference the transaction ID here, this is stored in the preapproval_key as this
# field is not used for amazon
#
self.raw_response = self.connection.get_transaction_status(transaction.preapproval_key)
self.response = self.raw_response[0]
logging.debug("Amazon PAYMENTDETAILS API for transaction id: %d returned response:")
logging.debug(self.response)
#
# Now we need to build values to match the paypal response.
# The two we need are status and and array of transactions.
#
# Check our status codes, note that these are different than the IPN status codes
self.local_status = self.response.StatusCode
self.message = self.response.StatusMessage
if self.local_status == 'Canceled':
self.status = TRANSACTION_STATUS_CANCELED
elif self.local_status == 'Success':
#
# Note, there is a limitation here. If the current status is refunded, this API will return "Success".
# We must be careful to not overwrite refunded status codes. There is no way that I can find to poll
# to see if a transaction is refunded. I need to investigate all of the data fields and see if we can find
# that information
#
if transaction.status != TRANSACTION_STATUS_REFUNDED:
self.status = TRANSACTION_STATUS_COMPLETE_PRIMARY
else:
self.status = TRANSACTION_STATUS_REFUNDED
elif self.local_status == 'PendingNetworkResponse' or self.local_status == 'PendingVerification':
self.status = TRANSACTION_STATUS_PENDING
elif self.local_status == 'TransactionDenied':
self.status = TRANSACTION_STATUS_FAILED
else:
self.status = TRANSACTION_STATUS_ERROR
# Amazon does not support receivers at this point
self.transactions = []
logging.debug("Amazon PAYMENTDETAILS API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon PAYMENTDETAILS api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon PAYMENTDETAILS FAILED with exception:")
self.errorMessage = "Error: ServerError"
traceback.print_exc()
class CancelPreapproval(AmazonRequest):
'''
Cancels an exisiting token. The current boto FPS library does not directly support
the CancelToken API, just the Cancel API(for real money in-flight or reserved).
'''
def __init__(self, transaction):
try:
logging.debug("Amazon CANCELPREAPPROVAL api called for transaction id: %d" % transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
params = {}
params['TokenId'] = transaction.pay_key
params['ReasonText'] = "Cancel Reason"
fps_response = self.connection.make_request("CancelToken", params)
body = fps_response.read()
print body
if(fps_response.status == 200):
rs = ResultSet()
h = handler.XmlHandler(rs, self)
xml.sax.parseString(body, h)
if rs:
self.raw_response = rs
self.response = self.raw_response[0]
self.status = self.response.TransactionStatus
self.errorMessage = None
else:
#
# Set an error message and failure status for
# our success() and error() functions
#
self.status = AMAZON_STATUS_FAILURE
self.errorMessage = "%s - %s" % (fps_response.reason, body)
logging.debug("Amazon CANCELPREAPPROVAL API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon CANCELPREAPPROVAL api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon CANCELPREAPPROVAL FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class RefundPayment(AmazonRequest):
def __init__(self, transaction):
try:
logging.debug("Amazon REFUNDPAYMENT API called for transaction id: %d", transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
if not transaction.preapproval_key:
# This is where we store the transaction ID
self.errorMessage = "No Valid Transaction ID"
return
#
# We need to reference the transaction ID here, this is stored in the preapproval_key as this
# field is not used for amazon
#
self.raw_response = self.connection.refund(transaction.secret, transaction.preapproval_key)
self.response = self.raw_response[0]
logging.debug("Amazon REFUNDPAYMENT response was:")
logging.debug(str(self.response))
self.status = self.response.TransactionStatus
logging.debug("Amazon REFUNDPAYMENT API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon REFUNDPAYMENT api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
logging.error("Amazon REFUNDPAYMENT FAILED with exception:")
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class PreapprovalDetails(AmazonRequest):
'''
Get details about an authorized token
This api must set 4 different class variables to work with the code in manager.py
status - one of the global transaction status codes
approved - boolean value
currency - not used in this API, but we can get some more info via other APIs - TODO
amount - not used in this API, but we can get some more info via other APIs - TODO
'''
def __init__(self, transaction=None):
try:
logging.debug("Amazon PREAPPROVALDETAILS API called for transaction id: %d", transaction.id)
# Use the boto class top open a connection
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
self.transaction = transaction
#
# We need to reference the caller reference here, we may not have a token if the return URL failed
#
self.raw_response = self.connection.get_token_by_caller_reference(transaction.secret)
self.response = self.raw_response
logging.debug("Amazon PREAPPROVALDETAILS response:")
logging.debug(str(self.response))
#
# Look for a token, we store this in the pay_key field
#
self.pay_key = self.response.TokenId
self.local_status = self.response.TokenStatus
# Possible status for the Token object are Active and Inactive
if self.local_status == 'Active':
self.status = TRANSACTION_STATUS_ACTIVE
self.approved = True
else:
# It is not clear here if this should be failed or cancelled, but we have no way to know
# the token is only active or now, so we will assume it is canceled.
self.status = TRANSACTION_STATUS_CANCELED
self.approved = False
# Set the other fields that are expected. We don't have values for these now, so just copy the transaction
self.currency = transaction.currency
self.amount = transaction.amount
logging.debug("Amazon PREAPPROVALDETAILS API returning with variables:")
logging.debug(locals())
except FPSResponseError as (responseStatus, responseReason, body):
logging.error("Amazon PREAPPROVALDETAILS api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
logging.error(body)
self.errorMessage = body
except:
# If the boto API fails, it also throws an exception and we end up here
logging.error("Amazon PREAPPROVALDETAILS FAILED with exception:")
self.errorMessage = "Error: ServerError"
traceback.print_exc()

View File

@ -2,11 +2,16 @@ 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
from django.conf import settings
from regluit.payment.parameters import *
from regluit.payment.paypal import Pay, Execute, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, IPN_PREAPPROVAL_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_NONE
from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_COMPLETED, CancelPreapproval, PaymentDetails, PreapprovalDetails, IPN_SENDER_STATUS_COMPLETED, IPN_TXN_STATUS_COMPLETED
from regluit.payment.paypal import RefundPayment
if settings.PAYMENT_PROCESSOR == 'paypal':
from regluit.payment.paypal import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
from regluit.payment.paypal import Pay as Execute
elif settings.PAYMENT_PROCESSOR == 'amazon':
from regluit.payment.amazon import Pay, Execute, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
import uuid
import traceback
from regluit.utils.localdatetime import now
@ -35,6 +40,11 @@ class PaymentManager( object ):
def __init__( self, embedded=False):
self.embedded = embedded
def processIPN(self, request):
# Forward to our payment processor
return ProcessIPN(request)
def update_preapproval(self, transaction):
"""Update a transaction to hold the data from a PreapprovalDetails on that transaction"""
@ -71,6 +81,16 @@ class PaymentManager( object ):
preapproval_status["approved"] = {'ours':t.approved, 'theirs':p.approved}
t.approved = p.approved
t.save()
# In amazon FPS, we may not have a pay_key via the return URL, update here
try:
if t.pay_key != p.pay_key:
preapproval_status['pay_key'] = {'ours':t.pay_key, 'theirs':p.pay_key}
t.pay_key = p.pay_key
t.save()
except:
# No problem, p.pay_key is not defined for paypal function
blah = "blah"
return preapproval_status
@ -92,12 +112,13 @@ class PaymentManager( object ):
payment_status['status'] = {'ours': t.status, 'theirs': p.status}
t.status = p.status
t.local_status = p.local_status
t.save()
receivers_status = []
for r in p.transactions:
# This is only supported for paypal at this time
try:
receiver = Receiver.objects.get(transaction=t, email=r['email'])
@ -184,113 +205,6 @@ class PaymentManager( object ):
return status
def processIPN(self, request):
'''
processIPN
Turns a request from Paypal into an IPN, and extracts info. We support 2 types of IPNs:
1) Payment - Used for instant payments and to execute pre-approved payments
2) Preapproval - Used for comfirmation of a preapproval
'''
try:
ipn = IPN(request)
if ipn.success():
logger.info("Valid IPN")
logger.info("IPN Transaction Type: %s" % ipn.transaction_type)
if ipn.transaction_type == IPN_TYPE_PAYMENT:
# payment IPN. we use our unique reference for the transaction as the key
# is only valid for 3 hours
uniqueID = ipn.uniqueID()
t = Transaction.objects.get(secret=uniqueID)
# The status is always one of the IPN_PAY_STATUS codes defined in paypal.py
t.status = ipn.status
for item in ipn.transactions:
try:
r = Receiver.objects.get(transaction=t, email=item['receiver'])
logger.info(item)
# one of the IPN_SENDER_STATUS codes defined in paypal.py, If we are doing delayed chained
# payments, then there is no status or id for non-primary receivers. Leave their status alone
r.status = item['status_for_sender_txn']
r.txn_id = item['id_for_sender_txn']
r.save()
except:
# Log an exception if we have a receiver that is not found. This will be hit
# for delayed chained payments as there is no status or id for the non-primary receivers yet
traceback.print_exc()
t.save()
logger.info("Final transaction status: %s" % t.status)
elif ipn.transaction_type == IPN_TYPE_ADJUSTMENT:
# a chargeback, reversal or refund for an existng payment
uniqueID = ipn.uniqueID()
if uniqueID:
t = Transaction.objects.get(secret=uniqueID)
else:
key = ipn.pay_key
t = Transaction.objects.get(pay_key=key)
# The status is always one of the IPN_PAY_STATUS codes defined in paypal.py
t.status = ipn.status
# Reason code indicates more details of the adjustment type
t.reason = ipn.reason_code
# Update the receiver status codes
for item in ipn.transactions:
try:
r = Receiver.objects.get(transaction=t, email=item['receiver'])
logger.info(item)
# one of the IPN_SENDER_STATUS codes defined in paypal.py, If we are doing delayed chained
# payments, then there is no status or id for non-primary receivers. Leave their status alone
r.status = item['status_for_sender_txn']
r.save()
except:
# Log an exception if we have a receiver that is not found. This will be hit
# for delayed chained payments as there is no status or id for the non-primary receivers yet
traceback.print_exc()
t.save()
elif ipn.transaction_type == IPN_TYPE_PREAPPROVAL:
# IPN for preapproval always uses the key to ref the transaction as this is always valid
key = ipn.preapproval_key
t = Transaction.objects.get(preapproval_key=key)
# The status is always one of the IPN_PREAPPROVAL_STATUS codes defined in paypal.py
t.status = ipn.status
# capture whether the transaction has been approved
t.approved = ipn.approved
t.save()
logger.info("IPN: Preapproval transaction: " + str(t.id) + " Status: " + ipn.status)
else:
logger.info("IPN: Unknown Transaction Type: " + ipn.transaction_type)
else:
logger.info("ERROR: INVALID IPN")
logger.info(ipn.error)
except:
traceback.print_exc()
def run_query(self, transaction_list, summary, pledged, authorized, incomplete, completed):
'''
@ -299,27 +213,27 @@ class PaymentManager( object ):
if pledged:
pledged_list = transaction_list.filter(type=PAYMENT_TYPE_INSTANT,
status=IPN_PAY_STATUS_COMPLETED)
status=TRANSACTION_STATUS_COMPLETE_PRIMARY)
else:
pledged_list = []
if authorized:
# return only ACTIVE transactions with approved=True
authorized_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PREAPPROVAL_STATUS_ACTIVE,
status=TRANSACTION_STATUS_ACTIVE,
approved=True)
else:
authorized_list = []
if incomplete:
incomplete_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PAY_STATUS_INCOMPLETE)
status=TRANSACTION_STATUS_INCOMPLETE)
else:
incomplete_list = []
if completed:
completed_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PAY_STATUS_COMPLETED)
status=TRANSACTION_STATUS_COMPLETE_PRIMARY)
else:
completed_list = []
@ -331,7 +245,7 @@ class PaymentManager( object ):
for t in pledged_list:
for r in t.receiver_set.all():
if r.status == IPN_TXN_STATUS_COMPLETED:
if r.status == TRANSACTION_STATUS_COMPLETED:
# or IPN_SENDER_STATUS_COMPLETED
# individual senders may not have been paid due to errors, and disputes/chargebacks only appear here
pledged_amount += r.amount
@ -420,7 +334,7 @@ class PaymentManager( object ):
'''
# only allow active transactions to go through again, if there is an error, intervention is needed
transactions = Transaction.objects.filter(campaign=campaign, status=IPN_PREAPPROVAL_STATUS_ACTIVE)
transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACTION_STATUS_ACTIVE)
for t in transactions:
@ -472,7 +386,7 @@ class PaymentManager( object ):
'''
transactions = Transaction.objects.filter(campaign=campaign, status=IPN_PREAPPROVAL_STATUS_ACTIVE)
transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACITON_STATUS_ACTIVE)
for t in transactions:
result = self.cancel_transaction(t)
@ -500,7 +414,7 @@ class PaymentManager( object ):
transaction.date_executed = now()
transaction.save()
p = Execute(transaction)
p = Finish(transaction)
# Create a response for this
envelope = p.envelope()
@ -554,7 +468,7 @@ class PaymentManager( object ):
transaction.date_payment = now()
transaction.save()
p = Pay(transaction)
p = Execute(transaction)
# Create a response for this
envelope = p.envelope()
@ -718,7 +632,7 @@ class PaymentManager( object ):
# Can only modify an active, pending transaction. If it is completed, we need to do a refund. If it is incomplete,
# then an IPN may be pending and we cannot touch it
if transaction.status != IPN_PREAPPROVAL_STATUS_ACTIVE:
if transaction.status != TRANSACTION_STATUS_ACTIVE:
logger.info("Error, attempt to modify a transaction that is not active")
return False, None
@ -782,7 +696,7 @@ class PaymentManager( object ):
# First check if a payment has been made. It is possible that some of the receivers may be incomplete
# We need to verify that the refund API will cancel these
if transaction.status != IPN_PAY_STATUS_COMPLETED:
if transaction.status != TRANSACTION_STATUS_COMPLETE_PRIMARY:
logger.info("Refund Transaction failed, invalid transaction status")
return False
@ -877,7 +791,7 @@ class PaymentManager( object ):
if p.success() and not p.error():
t.pay_key = p.key()
t.status = 'CREATED'
t.status = TRANSACTION_STATUS_CREATED
t.save()
if self.embedded:
@ -892,7 +806,7 @@ class PaymentManager( object ):
else:
t.error = p.error_string()
t.save()
logger.info("Pledge Error: " + p.error_string())
logger.info("Pledge Error: %s" % p.error_string())
return t, None

View File

@ -0,0 +1,177 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Transaction.local_status'
db.add_column('payment_transaction', 'local_status', self.gf('django.db.models.fields.CharField')(default='NONE', max_length=32, null=True), keep_default=False)
# Adding field 'PaymentResponse.status'
db.add_column('payment_paymentresponse', 'status', self.gf('django.db.models.fields.CharField')(max_length=32, null=True), keep_default=False)
# Adding field 'Receiver.local_status'
db.add_column('payment_receiver', 'local_status', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'Transaction.local_status'
db.delete_column('payment_transaction', 'local_status')
# Deleting field 'PaymentResponse.status'
db.delete_column('payment_paymentresponse', 'status')
# Deleting field 'Receiver.local_status'
db.delete_column('payment_receiver', 'local_status')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -16,8 +16,11 @@ class Transaction(models.Model):
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
# status: constants defined in paypal.py (e.g., IPN_PREAPPROVAL_STATUS_ACTIVE, IPN_PAY_STATUS_CREATED)
status = models.CharField(max_length=32, default='NONE', null=False)
# status: general status constants defined in parameters.py
status = models.CharField(max_length=32, default='None', null=False)
# local_status: status code specific to the payment processor
local_status = models.CharField(max_length=32, default='NONE', null=True)
# amount & currency -- amount of money and its currency involved for transaction
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
@ -99,6 +102,9 @@ class PaymentResponse(models.Model):
# extra info we want to store if an error occurs such as the response message
info = models.CharField(max_length=1024, null=True)
# local status specific to the api call
status = models.CharField(max_length=32, null=True)
transaction = models.ForeignKey(Transaction, null=False)
def __unicode__(self):
@ -113,6 +119,7 @@ class Receiver(models.Model):
currency = models.CharField(max_length=10)
status = models.CharField(max_length=64)
local_status = models.CharField(max_length=64, null=True)
reason = models.CharField(max_length=64)
primary = models.BooleanField()
txn_id = models.CharField(max_length=64)

View File

@ -12,6 +12,19 @@ TARGET_TYPE_CAMPAIGN = 1
TARGET_TYPE_LIST = 2
TARGET_TYPE_DONATION = 3
TRANSACTION_STATUS_NONE = 'None'
TRANSACTION_STATUS_CREATED = 'Created'
TRANSACTION_STATUS_COMPLETE = 'Complete'
TRANSACTION_STATUS_PENDING = 'Pending'
TRANSACTION_STATUS_COMPLETE_PRIMARY = 'Complete Primary'
TRANSACTION_STATUS_COMPLETE_SECONDARY = 'Complete Secondary'
TRANSACTION_STATUS_ACTIVE = 'Active'
TRANSACTION_STATUS_INCOMPLETE = 'Incomplete'
TRANSACTION_STATUS_ERROR = 'Error'
TRANSACTION_STATUS_CANCELED = 'Canceled'
TRANSACTION_STATUS_REFUNDED = 'Refunded'
TRANSACTION_STATUS_FAILED = 'Failed'
# these two following parameters are probably extraneous since I think we will compute dynamically where to return each time.
COMPLETE_URL = '/paymentcomplete'
CANCEL_URL = '/paymentcancel'

View File

@ -1,7 +1,7 @@
from regluit.payment.parameters import *
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.models import Transaction
from regluit.payment.models import Transaction, Receiver
from django.contrib.auth.models import User
from django.utils import simplejson as json
from django.utils.xmlutils import SimplerXMLGenerator
@ -105,6 +105,113 @@ IPN_REASON_CODE_CHARGEBACK_SETTLEMENT = 'Chargeback Settlement'
IPN_REASON_CODE_ADMIN_REVERSAL = 'Admin reversal'
IPN_REASON_CODE_REFUND = 'Refund'
def ProcessIPN(request):
'''
processIPN
Turns a request from Paypal into an IPN, and extracts info. We support 2 types of IPNs:
1) Payment - Used for instant payments and to execute pre-approved payments
2) Preapproval - Used for comfirmation of a preapproval
'''
try:
ipn = IPN(request)
if ipn.success():
logger.info("Valid IPN")
logger.info("IPN Transaction Type: %s" % ipn.transaction_type)
if ipn.transaction_type == IPN_TYPE_PAYMENT:
# payment IPN. we use our unique reference for the transaction as the key
# is only valid for 3 hours
uniqueID = ipn.uniqueID()
t = Transaction.objects.get(secret=uniqueID)
# The status is always one of the IPN_PAY_STATUS codes defined in paypal.py
t.local_status = ipn.status
for item in ipn.transactions:
try:
r = Receiver.objects.get(transaction=t, email=item['receiver'])
logger.info(item)
# one of the IPN_SENDER_STATUS codes defined in paypal.py, If we are doing delayed chained
# payments, then there is no status or id for non-primary receivers. Leave their status alone
r.status = item['status_for_sender_txn']
r.txn_id = item['id_for_sender_txn']
r.save()
except:
# Log an exception if we have a receiver that is not found. This will be hit
# for delayed chained payments as there is no status or id for the non-primary receivers yet
traceback.print_exc()
t.save()
logger.info("Final transaction status: %s" % t.status)
elif ipn.transaction_type == IPN_TYPE_ADJUSTMENT:
# a chargeback, reversal or refund for an existng payment
uniqueID = ipn.uniqueID()
if uniqueID:
t = Transaction.objects.get(secret=uniqueID)
else:
key = ipn.pay_key
t = Transaction.objects.get(pay_key=key)
# The status is always one of the IPN_PAY_STATUS codes defined in paypal.py
t.local_status = ipn.status
# Reason code indicates more details of the adjustment type
t.reason = ipn.reason_code
# Update the receiver status codes
for item in ipn.transactions:
try:
r = Receiver.objects.get(transaction=t, email=item['receiver'])
logger.info(item)
# one of the IPN_SENDER_STATUS codes defined in paypal.py, If we are doing delayed chained
# payments, then there is no status or id for non-primary receivers. Leave their status alone
r.status = item['status_for_sender_txn']
r.save()
except:
# Log an exception if we have a receiver that is not found. This will be hit
# for delayed chained payments as there is no status or id for the non-primary receivers yet
traceback.print_exc()
t.save()
elif ipn.transaction_type == IPN_TYPE_PREAPPROVAL:
# IPN for preapproval always uses the key to ref the transaction as this is always valid
key = ipn.preapproval_key
t = Transaction.objects.get(preapproval_key=key)
# The status is always one of the IPN_PREAPPROVAL_STATUS codes defined in paypal.py
t.local_status = ipn.status
# capture whether the transaction has been approved
t.approved = ipn.approved
t.save()
logger.info("IPN: Preapproval transaction: " + str(t.id) + " Status: " + ipn.status)
else:
logger.info("IPN: Unknown Transaction Type: " + ipn.transaction_type)
else:
logger.info("ERROR: INVALID IPN")
logger.info(ipn.error)
except:
traceback.print_exc()
class PaypalError(RuntimeError):
pass
@ -358,7 +465,7 @@ class Pay( PaypalEnvelopeRequest ):
class Execute(PaypalEnvelopeRequest):
class Finish(PaypalEnvelopeRequest):
def __init__(self, transaction=None):
@ -450,7 +557,9 @@ class PaymentDetails(PaypalEnvelopeRequest):
self.response = json.loads( self.raw_response )
logger.info(self.response)
self.local_status = self.response.get("status", None)
self.status = self.response.get("status", None)
self.trackingId = self.response.get("trackingId", None)
self.feesPayer = self.response.get("feesPayer", None)
payment_info_list = self.response.get("paymentInfoList", None)
@ -799,4 +908,6 @@ class IPN( object ):
self.transactions.append(transdict)
logger.info(transdict)

View File

@ -0,0 +1,14 @@
{% load amazon_fps_tags %}
{% block content %}
<div>
<p>You are going to be charged $100 in the
<a href="https://payments.amazon.com/sdui/sdui/helpTab/Amazon-Flexible-Payments-Service/Technical-Resources/Amazon-FPS-Sandbox">Amazon FPS Sandbox</a>.</p>
<p>{% amazon_fps fps_obj %}</p>
</div>
<div>
<p>(Recurring payments) You are going to be charged $100 every hour in the
<a href="https://payments.amazon.com/sdui/sdui/helpTab/Amazon-Flexible-Payments-Service/Technical-Resources/Amazon-FPS-Sandbox">Amazon FPS Sandbox</a>.</p>
<p>{% amazon_fps fps_recur_obj %}</p>
</div>
{% endblock %}

View File

View File

@ -0,0 +1,8 @@
"""
Template tags for amazon_fps
"""
from django import template
from billing.templatetags.amazon_fps_tags import amazon_fps
register = template.Library()
register.tag(amazon_fps)

View File

@ -123,6 +123,29 @@ def paySandbox(test, selenium, url, authorize=False, already_at_url=False, sleep
print "Tranasction Complete"
def payAmazonSandbox(sel):
login_email = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input#ap_email"))
login_email.click()
login_email.clear()
login_email.send_keys('supporter1@raymondyee.net')
login_password = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input#ap_password"))
login_password.click()
login_password.clear()
login_password.send_keys('testpw__')
submit_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input#signInSubmit"))
submit_button.click()
time.sleep(2)
# sel.find_element_by_css_selector("input[type='image']")
print "looking for credit_card_confirm", sel.current_url
credit_card_confirm = WebDriverWait(sel,20).until(lambda d: d.find_elements_by_css_selector("input[type='image']"))
credit_card_confirm[0].click()
print "looking for payment_confirm", sel.current_url
payment_confirm = WebDriverWait(sel,20).until(lambda d: d.find_elements_by_css_selector("input[type='image']"))
time.sleep(1)
payment_confirm[-1].click()
class PledgeTest(TestCase):
def setUp(self):

View File

@ -1,9 +1,17 @@
from django.conf.urls.defaults import *
from django.conf import settings
urlpatterns = patterns(
"regluit.payment.views",
url(r"^paypalipn", "paypalIPN", name="PayPalIPN"),
url(r"^paypalipn", "handleIPN", name="HandleIPN"),
url(r"^amazonipn", "handleIPN", name="HandleIPN"),
)
# Amazon payment URLs
urlpatterns += patterns(
"regluit.payment.amazon",
url(r"^amazonpaymentreturn", "amazonPaymentReturn", name="AmazonPaymentReturn"),
)
if not settings.IS_PREVIEW:
@ -19,5 +27,7 @@ if not settings.IS_PREVIEW:
url(r"^checkstatus", "checkStatus"),
url(r"^testfinish", "testFinish"),
url(r"^testrefund", "testRefund"),
url(r"^testmodify", "testModify"),
)
url(r"^testmodify", "testModify"),
)

View File

@ -3,20 +3,31 @@ from regluit.payment.paypal import IPN
from regluit.payment.models import Transaction
from regluit.core.models import Campaign, Wishlist
from django.conf import settings
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response
from django.contrib.auth.models import User
from django.contrib.sites.models import RequestSite
from regluit.payment.parameters import *
from django.http import HttpResponse, HttpRequest, HttpResponseRedirect
from django.http import HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.test.utils import setup_test_environment
from django.template import RequestContext
from unittest import TestResult
from regluit.payment.tests import PledgeTest, AuthorizeTest
import uuid
from decimal import Decimal as D
from regluit.utils.localdatetime import now
import traceback
import logging
logger = logging.getLogger(__name__)
# parameterize some test recipients
TEST_RECEIVERS = ['seller_1317463643_biz@gmail.com', 'Buyer6_1325742408_per@gmail.com']
TEST_RECEIVERS = ['raymond.yee@gmail.com', 'Buyer6_1325742408_per@gmail.com']
#TEST_RECEIVERS = ['seller_1317463643_biz@gmail.com', 'Buyer6_1325742408_per@gmail.com']
#TEST_RECEIVERS = ['glueja_1317336101_biz@gluejar.com', 'rh1_1317336251_biz@gluejar.com', 'RH2_1317336302_biz@gluejar.com']
@ -63,7 +74,7 @@ def testExecute(request):
logger.info(str(t))
else:
transactions = Transaction.objects.filter(status='ACTIVE')
transactions = Transaction.objects.filter(status=TRANSACTION_STATUS_ACTIVE)
for t in transactions:
@ -101,13 +112,13 @@ def testAuthorize(request):
# Note, set this to 1-5 different receivers with absolute amounts for each
receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00},
{'email': TEST_RECEIVERS[1], 'amount':10.00}]
if campaign_id:
campaign = Campaign.objects.get(id=int(campaign_id))
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, amount, campaign=campaign, list=None, user=None)
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, list=None, user=None)
t, url = p.authorize('USD', TARGET_TYPE_NONE, amount, campaign=None, return_url=None, list=None, user=None)
if url:
logger.info("testAuthorize: " + url)
@ -175,8 +186,8 @@ def testModify(request):
t = Transaction.objects.get(id=int(request.GET['transaction']))
p = PaymentManager()
status, url = p.modify_transaction(t, amount)
status, url = p.modify_transaction(t, amount, return_url=None)
if url:
logger.info("testModify: " + url)
@ -240,18 +251,18 @@ def testPledge(request):
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)
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)
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)
return HttpResponseRedirect(url)
else:
response = t.reference
logger.info("testPledge: Error " + str(t.reference))
response = t.error
logger.info("testPledge: Error " + str(t.error))
return HttpResponse(response)
def runTests(request):
@ -283,7 +294,7 @@ def runTests(request):
traceback.print_exc()
@csrf_exempt
def paypalIPN(request):
def handleIPN(request):
# Handler for paypal IPN notifications
p = PaymentManager()
@ -292,6 +303,7 @@ def paypalIPN(request):
logger.info(str(request.POST))
return HttpResponse("ipn")
def paymentcomplete(request):
# pick up all get and post parameters and display
output = "payment complete"
@ -305,7 +317,10 @@ def checkStatus(request):
return HttpResponse(error_data, mimetype="text/xml")
# https://raw.github.com/agiliq/merchant/master/example/app/views.py
def _render(request, template, template_vars={}):
return render_to_response(template, template_vars, RequestContext(request))

View File

@ -21,4 +21,7 @@ django-selectable
pytz
django-notification
boto
fabric
fabric
git+git://github.com/agiliq/merchant.git#egg=django-merchant
paramiko
pyasn1

View File

@ -12,6 +12,7 @@ django-debug-toolbar==0.8.5
django-endless-pagination==1.1
django-extensions==0.7.1
django-kombu==0.9.4
django-merchant==0.0.3a0
django-nose-selenium==0.7.3
django-notification==0.2
django-picklefield==0.1.9
@ -23,6 +24,7 @@ django-social-auth==0.6.1
#https://github.com/toastdriven/django-tastypie/tarball/master
django-tastypie==0.9.11
Django==1.3.1
Fabric==1.4.1
feedparser==5.1
freebase==1.0.8
httplib2==0.7.2
@ -34,6 +36,8 @@ mimeparse==0.1.3
MySQL-python==1.2.3
nose==1.1.2
oauth2==1.5.211
paramiko==1.7.7.1
pyasn1==0.1.3
pycrypto==2.5
pyparsing==1.5.6
python-dateutil==1.5
@ -47,4 +51,4 @@ requests==0.9.1
selenium==2.20.0
South==0.7.3
ssh==1.7.13
wsgiref==0.1.2
wsgiref==0.1.2

View File

@ -98,7 +98,7 @@ TEMPLATE_DIRS = (
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
@ -118,6 +118,7 @@ INSTALLED_APPS = (
'endless_pagination',
'selectable',
'regluit.frontend.templatetags',
'regluit.payment.templatetags',
'notification',
# this must appear *after* django.frontend or else it overrides the
@ -253,4 +254,26 @@ EBOOK_NOTIFICATIONS_JOB = {
"args": ()
}
# by default, in common, we don't turn any of the celerybeat jobs on -- turn them on in the local settings file
# by default, in common, we don't turn any of the celerybeat jobs on -- turn them on in the local settings file
# set -- sandbox or production Amazon FPS?
AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
#AMAZON_FPS_HOST = "fps.amazonaws.com"
# amazon or paypal for now.
PAYMENT_PROCESSOR = 'amazon'
# now import AWS/FPS keys from a separate file, if it exists
# Amazon FPS Credentials
#FPS_ACCESS_KEY = ''
#FPS_SECRET_KEY = ''
try:
from regluit.settings.aws import FPS_ACCESS_KEY, FPS_SECRET_KEY
except:
pass

View File

@ -65,6 +65,21 @@ GOOGLE_DISPLAY_NAME = 'unglue it!'
# https://code.google.com/apis/console
GOOGLE_BOOKS_API_KEY = ''
# Payment processor switch
PAYMENT_PROCESSOR = 'amazon'
# Amazon credentials (for fps)
AWS_ACCESS_KEY = ''
AWS_SECRET_ACCESS_KEY = ''
# Amazon FPS Credentials
FPS_ACCESS_KEY = ''
FPS_SECRET_KEY = ''
# set -- sandbox or production Amazon FPS?
AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
#AMAZON_FPS_HOST = "fps.amazonaws.com"
PAYPAL_USERNAME = ''
PAYPAL_PASSWORD = ''
PAYPAL_SIGNATURE = ''
@ -129,5 +144,4 @@ UNGLUEIT_TEST_PASSWORD = None
# decide which of the period tasks to add to the schedule
#CELERYBEAT_SCHEDULE['send_test_email'] = SEND_TEST_EMAIL_JOB
#CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB

View File

@ -124,3 +124,7 @@ IS_PREVIEW = False
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
# Amazon credentials (for fps)
AWS_ACCESS_KEY = ''
AWS_SECRET_ACCESS_KEY = ''

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

266
sysadmin/aws.py Normal file
View File

@ -0,0 +1,266 @@
from pprint import pprint
import os
import time
import boto
import boto.manage.cmdshell
# connect up parts of the Amazon infrastructure
# notes: to delete snapshots I have made of instances, one has to deregister the AMI first and then delete the snapshot
GLUEJAR_ACCOUNT_ID = 439256357102
ec2 = boto.connect_ec2()
cw = boto.connect_cloudwatch()
rds = boto.connect_rds()
def all_instances():
reservations = ec2.get_all_instances()
instances = [i for r in reservations for i in r.instances]
return instances
def all_zones():
print ec2.get_all_zones()
def all_rds():
return rds.get_all_dbinstances()
def all_rds_parameter_groups():
return rds.get_all_dbparameter_groups()
def modify_rds_parameter(group_name, parameter, value, apply_immediate=False):
"""change parameter in RDS parameter group_name to value
http://stackoverflow.com/a/9085381/7782
Remember to make sure that the parameter group is actually associated with the db.
You will likely need to reboot db too.
"""
pg = rds.get_all_dbparameters(group_name)
while not pg.has_key(parameter) and hasattr(pg, 'Marker'):
pg = rds.get_all_dbparameters(group_name, marker = pg.Marker)
if pg.has_key(parameter):
pg[parameter].value = value
pg[parameter].apply(immediate=apply_immediate)
return True
else:
return False
# how I associated the param_group to a db and then rebooted db
# rds.modify_dbinstance(id='production', param_group='production1', apply_immediately=True)
# rds.reboot_dbinstance(id='production')
def all_snapshots(owner=GLUEJAR_ACCOUNT_ID):
"""by default, return only snapshots owned by Gluejar -- None returns all snapshots available to us"""
return ec2.get_all_snapshots(owner=owner)
def instance(tag_name):
try:
return ec2.get_all_instances(filters={'tag:Name' : tag_name})[0].instances[0]
except Exception, e:
return None
def all_images(owners=(GLUEJAR_ACCOUNT_ID, )):
return ec2.get_all_images(owners=owners)
def stop_instances(instances):
return ec2.stop_instances(instance_ids=[instance.id for instance in instances])
def console_output(instance):
"""returnn console output of instance"""
try:
return instance.get_console_output().output
except Exception, e:
return None
def instance_metrics(instance):
"""metrics that apply to given instance"""
metrics = cw.list_metrics()
my_metrics = []
for metric in metrics:
if 'InstanceId' in metric.dimensions:
if instance.id in metric.dimensions['InstanceId']:
my_metrics.append(metric)
return my_metrics
# how to get average CPU utilization of web1?
# filter(lambda x: x.name == 'CPUUtilization', m)
# metric.query(start_time, end_time,'Average', period=6000)
def instance_metric(instance, metric_name):
m = instance_metrics(instance)
return filter(lambda x: x.name == metric_name, m)
def launch_time(instance):
return boto.utils.parse_ts(instance.launch_time)
def max_cpu(instance):
pass
def stats_for_instances(instances=None):
"""return basic stats for input instances"""
if instances is None:
instances = all_instances()
stats = []
for instance in instances:
instance.update() # to get latest update
stats.append((instance.id, instance.key_name, instance.state, instance.ip_address, instance.dns_name))
return stats
# http://my.safaribooksonline.com/book/-/9781449308100/2dot-ec2-recipes/id2529379
def launch_instance(ami='ami-a29943cb',
instance_type='t1.micro',
key_name='rdhyee_public_key',
key_extension='.pem',
key_dir='~/.ssh',
group_name='default',
ssh_port=22,
cidr='0.0.0.0/0',
tag='paws',
user_data=None,
cmd_shell=True,
login_user='ubuntu',
ssh_pwd=None):
"""
Launch an instance and wait for it to start running.
Returns a tuple consisting of the Instance object and the CmdShell
object, if request, or None.
ami The ID of the Amazon Machine Image that this instance will
be based on. Default is a 64-bit Amazon Linux EBS image.
instance_type The type of the instance.
key_name The name of the SSH Key used for logging into the instance.
It will be created if it does not exist.
key_extension The file extension for SSH private key files.
key_dir The path to the directory containing SSH private keys.
This is usually ~/.ssh.
group_name The name of the security group used to control access
to the instance. It will be created if it does not exist.
ssh_port The port number you want to use for SSH access (default 22).
cidr The CIDR block used to limit access to your instance.
tag A name that will be used to tag the instance so we can
easily find it later.
user_data Data that will be passed to the newly started
instance at launch and will be accessible via
the metadata service running at http://169.254.169.254.
cmd_shell If true, a boto CmdShell object will be created and returned.
This allows programmatic SSH access to the new instance.
login_user The user name used when SSH'ing into new instance. The
default is 'ubuntu'
ssh_pwd The password for your SSH key if it is encrypted with a
passphrase.
"""
cmd = None
# Create a connection to EC2 service.
# You can pass credentials in to the connect_ec2 method explicitly
# or you can use the default credentials in your ~/.boto config file
# as we are doing here.
ec2 = boto.connect_ec2()
# Check to see if specified keypair already exists.
# If we get an InvalidKeyPair.NotFound error back from EC2,
# it means that it doesn't exist and we need to create it.
try:
key = ec2.get_all_key_pairs(keynames=[key_name])[0]
except ec2.ResponseError, e:
if e.code == 'InvalidKeyPair.NotFound':
print 'Creating keypair: %s' % key_name
# Create an SSH key to use when logging into instances.
key = ec2.create_key_pair(key_name)
# AWS will store the public key but the private key is
# generated and returned and needs to be stored locally.
# The save method will also chmod the file to protect
# your private key.
key.save(key_dir)
else:
raise
# Check to see if specified security group already exists.
# If we get an InvalidGroup.NotFound error back from EC2,
# it means that it doesn't exist and we need to create it.
try:
group = ec2.get_all_security_groups(groupnames=[group_name])[0]
except ec2.ResponseError, e:
if e.code == 'InvalidGroup.NotFound':
print 'Creating Security Group: %s' % group_name
# Create a security group to control access to instance via SSH.
group = ec2.create_security_group(group_name,
'A group that allows SSH access')
else:
raise
# Add a rule to the security group to authorize SSH traffic
# on the specified port.
try:
group.authorize('tcp', ssh_port, ssh_port, cidr)
except ec2.ResponseError, e:
if e.code == 'InvalidPermission.Duplicate':
print 'Security Group: %s already authorized' % group_name
else:
raise
# Now start up the instance. The run_instances method
# has many, many parameters but these are all we need
# for now.
reservation = ec2.run_instances(ami,
key_name=key_name,
security_groups=[group_name],
instance_type=instance_type,
user_data=user_data)
# Find the actual Instance object inside the Reservation object
# returned by EC2.
instance = reservation.instances[0]
# The instance has been launched but it's not yet up and
# running. Let's wait for its state to change to 'running'.
print 'waiting for instance'
while instance.state != 'running':
print '.'
time.sleep(5)
instance.update()
print 'done'
# Let's tag the instance with the specified label so we can
# identify it later.
instance.add_tag(tag)
# The instance is now running, let's try to programmatically
# SSH to the instance using Paramiko via boto CmdShell.
if cmd_shell:
key_path = os.path.join(os.path.expanduser(key_dir),
key_name+key_extension)
cmd = boto.manage.cmdshell.sshclient_from_instance(instance,
key_path,
user_name=login_user,
ssh_pwd=ssh_pwd)
return (instance, cmd)
if __name__ == '__main__':
pprint (stats_for_instances(all_instances()))
web1 = instance('web1')
print instance_metrics(web1)

View File

@ -1,103 +0,0 @@
from pprint import pprint
import boto
# connect up parts of the Amazon infrastructure
# notes: to delete snapshots I have made of instances, one has to deregister the AMI first and then delete the snapshot
GLUEJAR_ACCOUNT_ID = 439256357102
ec2 = boto.connect_ec2()
cw = boto.connect_cloudwatch()
rds = boto.connect_rds()
def all_instances():
reservations = ec2.get_all_instances()
instances = [i for r in reservations for i in r.instances]
return instances
def all_zones():
print ec2.get_all_zones()
def all_rds():
return rds.get_all_dbinstances()
def all_rds_parameter_groups():
return rds.get_all_dbparameter_groups()
def modify_please1_pg_group():
"""kinda ugly
http://stackoverflow.com/a/9085381/7782
After doing this, I changed please db to talk to this parameter group and rebooted db
"""
pg = conn.get_all_dbparameters('mygroup')
pg2 = rds.get_all_dbparameters('mygroup', marker = pg.Marker)
pg2['tx_isolation'].value = True
pg2['tx_isolation'].apply(True)
def all_snapshots(owner=GLUEJAR_ACCOUNT_ID):
"""by default, return only snapshots owned by Gluejar -- None returns all snapshots available to us"""
return ec2.get_all_snapshots(owner=owner)
def instance(tag_name):
try:
return ec2.get_all_instances(filters={'tag:Name' : tag_name})[0].instances[0]
except Exception, e:
return None
def all_images(owners=(GLUEJAR_ACCOUNT_ID, )):
return ec2.get_all_images(owners=owners)
def stop_instances(instances):
return ec2.stop_instances(instance_ids=[instance.id for instance in instances])
def console_output(instance):
"""returnn console output of instance"""
try:
return instance.get_console_output().output
except Exception, e:
return None
def instance_metrics(instance):
"""metrics that apply to given instance"""
metrics = cw.list_metrics()
my_metrics = []
for metric in metrics:
if 'InstanceId' in metric.dimensions:
if instance.id in metric.dimensions['InstanceId']:
my_metrics.append(metric)
return my_metrics
# how to get average CPU utilization of web1?
# filter(lambda x: x.name == 'CPUUtilization', m)
# metric.query(start_time, end_time,'Average', period=6000)
def instance_metric(instance, metric_name):
m = instance_metrics(instance)
return filter(lambda x: x.name == metric_name, m)
def launch_time(instance):
return boto.utils.parse_ts(instance.launch_time)
def max_cpu(instance):
pass
def stats_for_instances(instances=None):
"""return basic stats for input instances"""
if instances is None:
instances = all_instances()
stats = []
for instance in instances:
instance.update() # to get latest update
stats.append((instance.id, instance.key_name, instance.state, instance.ip_address, instance.dns_name))
return stats
if __name__ == '__main__':
pprint (stats_for_instances(all_instances()))
web1 = instance('web1')
print instance_metrics(web1)

1746
test/campaign_starter.sql Normal file

File diff suppressed because one or more lines are too long

View File

@ -152,7 +152,7 @@ def recipient_status(clist):
# res = [pm.finish_campaign(c) for c in campaigns_incomplete()]
def support_campaign(do_local=True):
def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=True, backend='amazon'):
"""
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
@ -160,13 +160,13 @@ def support_campaign(do_local=True):
import django
django.db.transaction.enter_transaction_management()
UNGLUE_IT_URL = settings.LIVE_SERVER_TEST_URL
UNGLUE_IT_URL = unglue_it_url
# unglue.it login info
USER = settings.UNGLUEIT_TEST_USER
PASSWORD = settings.UNGLUEIT_TEST_PASSWORD
# PayPal developer sandbox
from regluit.payment.tests import loginSandbox, paySandbox
from regluit.payment.tests import loginSandbox, paySandbox, payAmazonSandbox
setup_selenium()
@ -182,9 +182,10 @@ def support_campaign(do_local=True):
time.sleep(10)
# find a campaign to pledge to
loginSandbox(sel)
time.sleep(2)
if backend == 'paypal':
loginSandbox(sel)
time.sleep(2)
print "now opening unglue.it"
#sel.get("http://www.google.com")
@ -241,9 +242,13 @@ def support_campaign(do_local=True):
# grab the URL where sel is now?
print "Now trying to pay PayPal", sel.current_url
paySandbox(None, sel, sel.current_url, authorize=True, already_at_url=True, sleep_time=5)
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)
# should be back on a pledge complete page
print sel.current_url, re.search(r"/pledge/complete",sel.current_url)
@ -270,7 +275,7 @@ def support_campaign(do_local=True):
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"))
@ -291,7 +296,10 @@ def support_campaign(do_local=True):
preapproval_amount_input.send_keys("25")
pledge_button = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("input[value*='Modify Pledge']"))
pledge_button.click()
paySandbox(None, sel, sel.current_url, authorize=True, already_at_url=True, sleep_time=5)
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)