Merge branch 'master' of github.com:Gluejar/regluit
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
|
@ -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"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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']
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 %}
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
||||
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
@ -21,4 +21,7 @@ django-selectable
|
|||
pytz
|
||||
django-notification
|
||||
boto
|
||||
fabric
|
||||
fabric
|
||||
git+git://github.com/agiliq/merchant.git#egg=django-merchant
|
||||
paramiko
|
||||
pyasn1
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 = ''
|
||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|