Updating payment code to support delayed chained payments. Adding entry points for delayed execution of payments. Creating common parent class for all paypal transactions to more accurately track metadata

pull/1/head
Jason 2011-12-14 08:30:37 -05:00
parent 7bfc4849a4
commit c2c3168e5a
6 changed files with 585 additions and 289 deletions

View File

@ -1,8 +1,8 @@
from regluit.core.models import Campaign, Wishlist
from regluit.payment.models import Transaction, Receiver
from regluit.payment.models import Transaction, Receiver, PaymentResponse
from django.contrib.auth.models import User
from regluit.payment.parameters import *
from regluit.payment.paypal import Pay, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, Preapproval, IPN_PAY_STATUS_COMPLETED, CancelPreapproval, PaymentDetails, PreapprovalDetails, IPN_SENDER_STATUS_COMPLETED, IPN_TXN_STATUS_COMPLETED
from regluit.payment.paypal import Pay, Execute, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, Preapproval, IPN_PAY_STATUS_COMPLETED, CancelPreapproval, PaymentDetails, PreapprovalDetails, IPN_SENDER_STATUS_COMPLETED, IPN_TXN_STATUS_COMPLETED
import uuid
import traceback
from datetime import datetime
@ -56,7 +56,7 @@ class PaymentManager( object ):
p = PaymentDetails(t)
if p.error():
if p.error() or not p.success():
logger.info("Error retrieving payment details for transaction %d" % t.id)
append_element(doc, tran, "error", "An error occurred while verifying this transaction, see server logs for details")
@ -73,15 +73,23 @@ class PaymentManager( object ):
try:
receiver = Receiver.objects.get(transaction=t, email=r['email'])
# Check for updates on each receiver's status
print r
print receiver
# Check for updates on each receiver's status. Note that unprocessed delayed chained payments
# will not have a status code or txn id code
if receiver.status != r['status']:
append_element(doc, tran, "receiver_status_ours", receiver.status)
append_element(doc, tran, "receiver_status_theirs", r['status'])
receiver.status = r['status']
receiver.txn_id = r['txn_id']
receiver.save()
if receiver.txn_id != r['txn_id']:
append_element(doc, tran, "txn_id_ours", receiver.txn_id)
append_element(doc, tran, "txn_id_theirs", r['txn_id'])
receiver.txn_id = r['txn_id']
receiver.save()
except:
traceback.print_exc()
@ -93,10 +101,10 @@ class PaymentManager( object ):
p = PreapprovalDetails(t)
tran = doc.createElement('preapproval')
tran.setAttribute("key", str(t.reference))
tran.setAttribute("key", str(t.preapproval_key))
head.appendChild(tran)
if p.error():
if p.error() or not p.success():
logger.info("Error retrieving preapproval details for transaction %d" % t.id)
append_element(doc, tran, "error", "An error occurred while verifying this transaction, see server logs for details")
@ -160,12 +168,14 @@ class PaymentManager( object ):
try:
r = Receiver.objects.get(transaction=t, email=item['receiver'])
logger.info(item)
# one of the IPN_SENDER_STATUS codes defined in paypal.py
# 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 excecption if we have a receiver that is not found
# 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()
@ -179,8 +189,8 @@ class PaymentManager( object ):
if uniqueID:
t = Transaction.objects.get(secret=uniqueID)
else:
key = ipn.key()
t = Transaction.objects.get(reference=key)
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
@ -192,8 +202,8 @@ class PaymentManager( object ):
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.key()
t = Transaction.objects.get(reference=key)
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
@ -326,6 +336,49 @@ class PaymentManager( object ):
return transactions
def finish_transaction(self, transaction):
'''
finish_transaction
calls the paypal API to excute payment to non-primary receivers
transaction: the transaction we want to complete
'''
if transaction.execution != EXECUTE_TYPE_CHAINED_DELAYED:
logger.error("FinishTransaction called with invalid execution type")
return False
# mark this transaction as executed
transaction.date_executed = datetime.now()
transaction.save()
p = Execute(transaction)
# Create a response for this
envelope = p.envelope()
if envelope:
correlation = p.correlation_id()
timestamp = p.timestamp()
r = PaymentResponse.objects.create(api=p.url,
correlation_id = correlation,
timestamp = timestamp,
info = p.raw_response,
transaction=transaction)
if p.success() and not p.error():
logger.info("finish_transaction Success")
return True
else:
transaction.error = p.error_string()
transaction.save()
logger.info("finish_transaction error " + p.error_string())
return False
def execute_transaction(self, transaction, receiver_list):
'''
execute_transaction
@ -357,15 +410,31 @@ class PaymentManager( object ):
p = Pay(transaction)
# We will update our transaction status when we receive the IPN
# Create a response for this
envelope = p.envelope()
if p.status() == IPN_PAY_STATUS_COMPLETED:
logger.info("Execute Success")
if envelope:
correlation = p.correlation_id()
timestamp = p.timestamp()
r = PaymentResponse.objects.create(api=p.api(),
correlation_id = correlation,
timestamp = timestamp,
info = p.raw_response,
transaction=transaction)
# We will update our transaction status when we receive the IPN
if p.success() and not p.error():
transaction.pay_key = p.key()
transaction.save()
logger.info("execute_transaction Success")
return True
else:
transaction.error = p.error()
logger.info("Execute Error: " + p.error())
transaction.error = p.error_string()
transaction.save()
logger.info("execute_transaction Error: " + p.error_string())
return False
def cancel(self, transaction):
@ -379,13 +448,28 @@ class PaymentManager( object ):
p = CancelPreapproval(transaction)
if p.success():
# Create a response for this
envelope = p.envelope()
if envelope:
correlation = p.correlation_id()
timestamp = p.timestamp()
r = PaymentResponse.objects.create(api=p.url,
correlation_id = correlation,
timestamp = timestamp,
info = p.raw_response,
transaction=transaction)
if p.success() and not p.error():
logger.info("Cancel Transaction " + str(transaction.id) + " Completed")
return True
else:
logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error())
transaction.error = p.error()
transaction.error = p.error_string()
transaction.save()
logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
return False
def authorize(self, currency, target, amount, campaign=None, list=None, user=None, return_url=None, cancel_url=None, anonymous=False):
@ -407,7 +491,8 @@ class PaymentManager( object ):
'''
t = Transaction.objects.create(amount=amount,
type=PAYMENT_TYPE_AUTHORIZATION,
type=PAYMENT_TYPE_AUTHORIZATION,
execution = EXECUTE_TYPE_CHAINED_DELAYED,
target=target,
currency=currency,
status='NONE',
@ -419,8 +504,18 @@ class PaymentManager( object ):
p = Preapproval(t, amount, return_url=return_url, cancel_url=cancel_url)
if p.status() == 'Success':
t.reference = p.paykey()
# Create a response for this
envelope = p.envelope()
if envelope:
r = PaymentResponse.objects.create(api=p.url,
correlation_id = p.correlation_id(),
timestamp = p.timestamp(),
info = p.raw_response,
transaction=t)
if p.success() and not p.error():
t.preapproval_key = p.key()
t.save()
url = p.next_url()
@ -430,9 +525,9 @@ class PaymentManager( object ):
else:
t.error = p.error()
t.error = p.error_string()
t.save()
logger.info("Authorize Error: " + p.error())
logger.info("Authorize Error: " + p.error_string())
return t, None
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None, return_url=None, cancel_url=None, anonymous=False):
@ -465,7 +560,8 @@ class PaymentManager( object ):
amount = D(receiver_list[0]['amount'])
t = Transaction.objects.create(amount=amount,
type=PAYMENT_TYPE_INSTANT,
type=PAYMENT_TYPE_INSTANT,
execution=EXECUTE_TYPE_CHAINED_INSTANT,
target=target,
currency=currency,
status='NONE',
@ -480,8 +576,19 @@ class PaymentManager( object ):
p = Pay(t,return_url=return_url, cancel_url=cancel_url)
if p.status() == 'CREATED':
t.reference = p.paykey()
# Create a response for this
envelope = p.envelope()
print envelope
if envelope:
r = PaymentResponse.objects.create(api=p.api(),
correlation_id = p.correlation_id(),
timestamp = p.timestamp(),
info = p.raw_response,
transaction=t)
if p.success() and not p.error():
t.pay_key = p.key()
t.status = 'CREATED'
t.save()
@ -495,9 +602,9 @@ class PaymentManager( object ):
return t, url
else:
t.error = p.error()
t.error = p.error_string()
t.save()
logger.info("Pledge Error: " + p.error())
logger.info("Pledge Error: " + p.error_string())
return t, None

View File

@ -4,7 +4,7 @@ from regluit.core.models import Campaign, Wishlist
from regluit.payment.parameters import *
from decimal import Decimal
import uuid
class Transaction(models.Model):
# type e.g., PAYMENT_TYPE_INSTANT or PAYMENT_TYPE_AUTHORIZATION -- defined in parameters.py
@ -13,6 +13,9 @@ class Transaction(models.Model):
# target: e.g, TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST -- defined in parameters.py
target = models.IntegerField(default=TARGET_TYPE_NONE, null=False)
#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_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_CREATED)
status = models.CharField(max_length=32, default='NONE', null=False)
@ -24,7 +27,10 @@ class Transaction(models.Model):
secret = models.CharField(max_length=64, null=True)
# a paykey that PayPal generates to identify this transaction
reference = models.CharField(max_length=128, null=True)
pay_key = models.CharField(max_length=128, null=True)
# a preapproval key that Paypal generates to identify this transaction
preapproval_key = models.CharField(max_length=128, null=True)
# (RY is not sure what receipt is for)
receipt = models.CharField(max_length=256, null=True)
@ -39,9 +45,12 @@ class Transaction(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
# date_payment: when an attempt is made to make the payment
# date_payment: when an attempt is made to make the primary payment
date_payment = models.DateTimeField(null=True)
# date_executed: when an attempt is made to send money to non-primary chained receivers
date_executed = models.DateTimeField(null=True)
# datetime for creation of preapproval and for its expiration
date_authorized = models.DateTimeField(null=True)
date_expired = models.DateTimeField(null=True)
@ -62,7 +71,7 @@ class Transaction(models.Model):
super(Transaction, self).save(*args, **kwargs) # Call the "real" save() method.
def __unicode__(self):
return u"-- Transaction:\n \tstatus: %s\n \t amount: %s\n \treference: %s\n \terror: %s\n" % (self.status, str(self.amount), self.reference, self.error)
return u"-- Transaction:\n \tstatus: %s\n \t amount: %s\n \treference: %s\n \terror: %s\n" % (self.status, str(self.amount), self.preapproval_key, self.error)
def create_receivers(self, receiver_list):
@ -72,6 +81,22 @@ class Transaction(models.Model):
primary = False
class PaymentResponse(models.Model):
# The API used
api = models.CharField(max_length=64, null=False)
# The correlation ID
correlation_id = models.CharField(max_length=512, null=True)
# the paypal timestamp
timestamp = models.CharField(max_length=128, null=True)
# extra info we want to store if an error occurs such as the response message
info = models.CharField(max_length=1024, null=True)
transaction = models.ForeignKey(Transaction, null=False)
class Receiver(models.Model):
email = models.CharField(max_length=64)
@ -92,10 +117,13 @@ import regluit.payment.manager
def handle_transaction_change(sender, instance, created, **kwargs):
campaign = instance.campaign
p = regluit.payment.manager.PaymentManager()
amount = p.query_campaign(campaign=instance.campaign,summary=True)
instance.campaign.left=instance.campaign.target - amount
instance.campaign.save()
if campaign:
p = regluit.payment.manager.PaymentManager()
amount = p.query_campaign(campaign=instance.campaign,summary=True)
instance.campaign.left=instance.campaign.target - amount
instance.campaign.save()
return True
post_save.connect(handle_transaction_change,sender=Transaction)

View File

@ -2,6 +2,12 @@ PAYMENT_TYPE_NONE = 0
PAYMENT_TYPE_INSTANT = 1
PAYMENT_TYPE_AUTHORIZATION = 2
EXECUTE_TYPE_NONE = 0
EXECUTE_TYPE_CHAINED_INSTANT = 1
EXECUTE_TYPE_CHAINED_DELAYED = 2
EXECUTE_TYPE_PARALLEL = 3
TARGET_TYPE_NONE = 0
TARGET_TYPE_CAMPAIGN = 1
TARGET_TYPE_LIST = 2

View File

@ -82,10 +82,10 @@ class PaypalError(RuntimeError):
class url_request( object ):
def __init__( self, base, url, data=None, headers={} ):
def __init__( self, request):
conn = httplib.HTTPSConnection(base)
conn.request("POST", url, data, headers)
conn = httplib.HTTPSConnection(settings.PAYPAL_ENDPOINT)
conn.request("POST", request.url, request.raw_request, request.headers)
#Check the response - should be 200 OK.
self.response = conn.getresponse()
@ -96,11 +96,241 @@ class url_request( object ):
def code( self ):
return self.response.status
class PaypalEnvelopeRequest:
'''
Handles common information that is processed from the response envelope of the paypal request.
All of our requests have a response envelope of the following format:
ack common:AckCode
Acknowledgement code. It is one of the following values:
Success - The operation completed successfully.
Failure - The operation failed.
Warning - Warning.
SuccessWithWarning - The operation completed successfully; however, there is a warning message.
FailureWithWarning - The operation failed with a warning message.
build Build number; it is used only by Developer Technical Support.
correlationId Correlation ID; it is used only by Developer Technical Support.
timestamp Date on which the response was sent. The time is currently not supported.
class Pay( object ):
Additionally, our subclasses may set the error_message field if an undetermined error occurs. Examples of undertmined errors are:
HTTP error codes(not 200)
Invalid parameters
Python exceptions during processing
All clients should check both the success() and the error() functions to determine the result of the operation
'''
# Global values for the class
response = None
raw_response = None
errorMessage = None
raw_request = None
url = None
def ack( self ):
if self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ):
return self.response['responseEnvelope']['ack']
else:
return None
def success(self):
status = self.ack()
print status
if status == "Success" or status == "SuccessWithWarning":
return True
else:
return False
def error(self):
message = self.errorMessage
print message
if message:
return True
else:
return False
def error_data(self):
if self.response.has_key('error'):
return self.response['error']
else:
return None
def error_id(self):
if self.response.has_key('error'):
return self.response['error'][0]['errorId']
def error_string(self):
if self.response.has_key('error'):
return self.response['error'][0]['message']
elif self.errorMessage:
return self.errorMessage
else:
return None
def envelope(self):
if self.response.has_key('responseEnvelope'):
return self.response['responseEnvelope']
else:
return None
def correlation_id(self):
if self.response.has_key('responseEnvelope') and self.response['responseEnvelope'].has_key('correlationId'):
return self.response['responseEnvelope']['correlationId']
else:
return None
def timestamp(self):
if self.response.has_key('responseEnvelope') and self.response['responseEnvelope'].has_key('timestamp'):
return self.response['responseEnvelope']['timestamp']
else:
return None
class Pay( PaypalEnvelopeRequest ):
def __init__( self, transaction, return_url=None, cancel_url=None):
headers = {
try:
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':settings.PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':settings.PAYPAL_SIGNATURE,
'X-PAYPAL-APPLICATION-ID':settings.PAYPAL_APPID,
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON'
}
if return_url is None:
return_url = settings.BASE_URL + COMPLETE_URL
if cancel_url is None:
cancel_url = settings.BASE_URL + CANCEL_URL
logger.info("Return URL: " + return_url)
logger.info("Cancel URL: " + cancel_url)
receiver_list = []
receivers = transaction.receiver_set.all()
if len(receivers) == 0:
raise Exception
# by setting primary_string of the first receiver to 'true', we are doing a Chained payment
for r in receivers:
if len(receivers) > 1:
if r.primary and (transaction.execution == EXECUTE_TYPE_CHAINED_INSTANT or transaction.execution == EXECUTE_TYPE_CHAINED_DELAYED):
# Only set a primary if we are using chained payments
primary_string = 'true'
else:
primary_string = 'false'
receiver_list.append({'email':r.email,'amount':str(r.amount), 'primary':primary_string})
else:
receiver_list.append({'email':r.email,'amount':str(r.amount)})
logger.info(receiver_list)
# actionType can be 'PAY', 'CREATE', or 'PAY_PRIMARY'
# PAY_PRIMARY': "For chained payments only, specify this value to delay payments to the secondary receivers; only the payment to the primary receiver is processed"
if transaction.execution == EXECUTE_TYPE_CHAINED_DELAYED:
self.actionType = 'PAY_PRIMARY'
else:
self.actionType = 'PAY'
# feesPayer: SENDER, PRIMARYRECEIVER, EACHRECEIVER, SECONDARYONLY
# if only one receiver, set to EACHRECEIVER, otherwise set to SECONDARYONLY
if len(receivers) == 1:
feesPayer = 'EACHRECEIVER'
else:
feesPayer = 'SECONDARYONLY'
data = {
'actionType': self.actionType,
'receiverList': { 'receiver': receiver_list },
'currencyCode': transaction.currency,
'returnUrl': return_url,
'cancelUrl': cancel_url,
'requestEnvelope': { 'errorLanguage': 'en_US' },
'ipnNotificationUrl': settings.BASE_URL + reverse('PayPalIPN'),
'feesPayer': feesPayer,
'trackingId': transaction.secret
}
logging.info("paypal PAY data: %s" % data)
print >> sys.stderr, "paypal PAY data:", data
# Is ipnNotificationUrl being computed properly
print >> sys.stderr, 'ipnNotificationUrl', settings.BASE_URL + reverse('PayPalIPN')
# a Pay operation can be for a payment that goes through immediately or for setting up a preapproval.
# transaction.reference is not null if it represents a preapproved payment, which has a preapprovalKey.
if transaction.preapproval_key:
data['preapprovalKey'] = transaction.preapproval_key
self.raw_request = json.dumps(data)
self.url = "/AdaptivePayments/Pay"
self.headers = headers
self.connection = url_request(self)
self.code = self.connection.code()
if self.code != 200:
self.errorMessage = 'PayPal response code was %i' % self.code
return
self.raw_response = self.connection.content()
print >> sys.stderr, "PAY request", settings.PAYPAL_ENDPOINT, "/AdaptivePayments/Pay", self.raw_request, headers
logger.info("paypal PAY response was: %s" % self.raw_response)
print >> sys.stderr, "paypal PAY response was:", self.raw_response
self.response = json.loads( self.raw_response )
logger.info(self.response)
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
def api(self):
return self.actionType
def exec_status( self ):
if self.response.has_key( 'paymentExecStatus' ):
return self.response['paymentExecStatus']
else:
return None
def amount( self ):
if self.response.has_key('payment_gross'):
return self.response['payment_gross']
else:
return None
def key( self ):
if self.response.has_key('payKey'):
return self.response['payKey']
else:
return None
def next_url( self ):
return '%s?cmd=_ap-payment&paykey=%s' % (settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] )
def embedded_url(self):
return '%s/webapps/adaptivepayment/flow/pay?paykey=%s&expType=light' % ( settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] )
class Execute(PaypalEnvelopeRequest):
def __init__(self, transaction=None):
try:
self.errorMessage = None
self.response = None
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':settings.PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':settings.PAYPAL_SIGNATURE,
@ -108,110 +338,47 @@ class Pay( object ):
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON'
}
if transaction.execution != EXECUTE_TYPE_CHAINED_DELAYED:
self.errorMessage = "Invalid transaction type for execution"
return
if not transaction.pay_key:
self.errorMessage = "No Paykey Found in transaction"
return
data = {
'payKey': transaction.pay_key,
'requestEnvelope': { 'errorLanguage': 'en_US' }
}
logging.info("paypal EXECUTE data: %s" % data)
self.raw_request = json.dumps(data)
self.url = "/AdaptivePayments/ExecutePayment"
self.headers = headers
self.connection = url_request(self)
self.code = self.connection.code()
if self.code != 200:
self.errorMessage = 'PayPal response code was %i' % self.code
return
self.raw_response = self.connection.content()
logger.info("paypal EXECUTE response was: %s" % self.raw_response)
self.response = json.loads( self.raw_response )
except:
traceback.print_exc()
self.errorMessage = "Error: Server error occurred"
if return_url is None:
return_url = settings.BASE_URL + COMPLETE_URL
if cancel_url is None:
cancel_url = settings.BASE_URL + CANCEL_URL
logger.info("Return URL: " + return_url)
logger.info("Cancel URL: " + cancel_url)
receiver_list = []
receivers = transaction.receiver_set.all()
if len(receivers) == 0:
raise Exception
# by setting primary_string of the first receiver to 'true', we are doing a Chained payment
for r in receivers:
if len(receivers) > 1:
if r.primary:
primary_string = 'true'
else:
primary_string = 'false'
receiver_list.append({'email':r.email,'amount':str(r.amount), 'primary':primary_string})
else:
receiver_list.append({'email':r.email,'amount':str(r.amount)})
logger.info(receiver_list)
# actionType can be 'PAY', 'CREATE', or 'PAY_PRIMARY'
# PAY_PRIMARY': "For chained payments only, specify this value to delay payments to the secondary receivers; only the payment to the primary receiver is processed"
# feesPayer: SENDER, PRIMARYRECEIVER, EACHRECEIVER, SECONDARYONLY
# if only one receiver, set to EACHRECEIVER, otherwise set to SECONDARYONLY
if len(receivers) == 1:
feesPayer = 'EACHRECEIVER'
else:
feesPayer = 'SECONDARYONLY'
data = {
'actionType': 'PAY',
'receiverList': { 'receiver': receiver_list },
'currencyCode': transaction.currency,
'returnUrl': return_url,
'cancelUrl': cancel_url,
'requestEnvelope': { 'errorLanguage': 'en_US' },
'ipnNotificationUrl': settings.BASE_URL + reverse('PayPalIPN'),
'feesPayer': feesPayer,
'trackingId': transaction.secret
}
logging.info("paypal PAY data: %s" % data)
print >> sys.stderr, "paypal PAY data:", data
# Is ipnNotificationUrl being computed properly
print >> sys.stderr, 'ipnNotificationUrl', settings.BASE_URL + reverse('PayPalIPN')
# a Pay operation can be for a payment that goes through immediately or for setting up a preapproval.
# transaction.reference is not null if it represents a preapproved payment, which has a preapprovalKey.
if transaction.reference:
data['preapprovalKey'] = transaction.reference
self.raw_request = json.dumps(data)
self.raw_response = url_request(settings.PAYPAL_ENDPOINT, "/AdaptivePayments/Pay", data=self.raw_request, headers=headers ).content()
print >> sys.stderr, "PAY request", settings.PAYPAL_ENDPOINT, "/AdaptivePayments/Pay", self.raw_request, headers
logger.info("paypal PAY response was: %s" % self.raw_response)
print >> sys.stderr, "paypal PAY response was:", self.raw_response
self.response = json.loads( self.raw_response )
logger.info(self.response)
def status( self ):
if self.response.has_key( 'paymentExecStatus' ):
return self.response['paymentExecStatus']
else:
return None
def error( self ):
if self.response.has_key('error'):
error = self.response['error']
logger.info(error)
return error[0]['message']
else:
return 'Paypal PAY: Unknown Error'
def amount( self ):
return decimal.Decimal(self.results[ 'payment_gross' ])
def paykey( self ):
return self.response['payKey']
def next_url( self ):
return '%s?cmd=_ap-payment&paykey=%s' % (settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] )
def embedded_url(self):
return '%s/webapps/adaptivepayment/flow/pay?paykey=%s&expType=light' % ( settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] )
class PaymentDetails(object):
class PaymentDetails(PaypalEnvelopeRequest):
def __init__(self, transaction=None):
try:
self.transaction = transaction
self.errorMessage = None
self.response = None
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
@ -230,14 +397,17 @@ class PaymentDetails(object):
}
self.raw_request = json.dumps(data)
self.connection = url_request(settings.PAYPAL_ENDPOINT, "/AdaptivePayments/PaymentDetails", data=self.raw_request, headers=headers )
self.raw_response = self.connection.content()
self.headers = headers
self.url = "/AdaptivePayments/PaymentDetails"
self.connection = url_request(self)
self.code = self.connection.code()
if self.code != 200:
self.errorMessage = 'PayPal response code was %i' % self.code
return
self.raw_response = self.connection.content()
logger.info("paypal PaymentDetails response was: %s" % self.raw_response)
self.response = json.loads( self.raw_response )
@ -266,17 +436,6 @@ class PaymentDetails(object):
self.errorMessage = "Error: ServerError"
traceback.print_exc()
def error(self):
if self.response and self.response.has_key('error'):
error = self.response['error']
logger.info(error)
return error[0]['message']
elif self.errorMessage:
return self.errorMessage
else:
return None
def compare(self):
"""compare current status information from what's in the current transaction object"""
@ -308,99 +467,109 @@ class PaymentDetails(object):
# paymentInfoList -- holds info for each recipient
class CancelPreapproval(object):
class CancelPreapproval(PaypalEnvelopeRequest):
def __init__(self, transaction):
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':settings.PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':settings.PAYPAL_SIGNATURE,
'X-PAYPAL-APPLICATION-ID':settings.PAYPAL_APPID,
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON',
}
data = {
'preapprovalKey':transaction.reference,
'requestEnvelope': { 'errorLanguage': 'en_US' }
}
self.raw_request = json.dumps(data)
self.raw_response = url_request(settings.PAYPAL_ENDPOINT, "/AdaptivePayments/CancelPreapproval", data=self.raw_request, headers=headers ).content()
logger.info("paypal CANCEL PREAPPROBAL response was: %s" % self.raw_response)
self.response = json.loads( self.raw_response )
logger.info(self.response)
def success(self):
if self.status() == 'Success' or self.status() == "SuccessWithWarning":
return True
else:
return False
def error(self):
if self.response.has_key('error'):
error = self.response['error']
logger.info(error)
return error[0]['message']
else:
return 'Paypal Preapproval Cancel: Unknown Error'
def status(self):
if self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ):
return self.response['responseEnvelope']['ack']
else:
return None
try:
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':settings.PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':settings.PAYPAL_SIGNATURE,
'X-PAYPAL-APPLICATION-ID':settings.PAYPAL_APPID,
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON',
}
data = {
'preapprovalKey':transaction.preapproval_key,
'requestEnvelope': { 'errorLanguage': 'en_US' }
}
self.raw_request = json.dumps(data)
self.headers = headers
self.url = "/AdaptivePayments/CancelPreapproval"
self.connection = url_request(self)
self.code = self.connection.code()
if self.code != 200:
self.errorMessage = 'PayPal response code was %i' % self.code
return
self.raw_response = self.connection.content()
logger.info("paypal CANCEL PREAPPROBAL response was: %s" % self.raw_response)
self.response = json.loads( self.raw_response )
logger.info(self.response)
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class Preapproval( object ):
class Preapproval( PaypalEnvelopeRequest ):
def __init__( self, transaction, amount, return_url=None, cancel_url=None):
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':settings.PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':settings.PAYPAL_SIGNATURE,
'X-PAYPAL-APPLICATION-ID':settings.PAYPAL_APPID,
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON',
}
if return_url is None:
return_url = settings.BASE_URL + COMPLETE_URL
if cancel_url is None:
cancel_url = settings.BASE_URL + CANCEL_URL
# set the expiration date for the preapproval
now = datetime.datetime.utcnow()
expiry = now + datetime.timedelta( days=PREAPPROVAL_PERIOD )
transaction.date_authorized = now
transaction.date_expired = expiry
transaction.save()
data = {
'endingDate': expiry.isoformat(),
'startingDate': now.isoformat(),
'maxTotalAmountOfAllPayments': '%.2f' % transaction.amount,
'maxNumberOfPayments':1,
'maxAmountPerPayment': '%.2f' % transaction.amount,
'currencyCode': transaction.currency,
'returnUrl': return_url,
'cancelUrl': cancel_url,
'requestEnvelope': { 'errorLanguage': 'en_US' },
'ipnNotificationUrl': settings.BASE_URL + reverse('PayPalIPN')
}
# Is ipnNotificationUrl being computed properly
print >> sys.stderr, 'ipnNotificationUrl', settings.BASE_URL + reverse('PayPalIPN')
self.raw_request = json.dumps(data)
self.raw_response = url_request(settings.PAYPAL_ENDPOINT, "/AdaptivePayments/Preapproval", data=self.raw_request, headers=headers ).content()
logger.info("paypal PREAPPROVAL response was: %s" % self.raw_response)
print >> sys.stderr, "paypal PREAPPROVAL response was:", self.raw_response
self.response = json.loads( self.raw_response )
logger.info(self.response)
def paykey( self ):
try:
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':settings.PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':settings.PAYPAL_SIGNATURE,
'X-PAYPAL-APPLICATION-ID':settings.PAYPAL_APPID,
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON',
}
if return_url is None:
return_url = settings.BASE_URL + COMPLETE_URL
if cancel_url is None:
cancel_url = settings.BASE_URL + CANCEL_URL
# set the expiration date for the preapproval
now = datetime.datetime.utcnow()
expiry = now + datetime.timedelta( days=PREAPPROVAL_PERIOD )
transaction.date_authorized = now
transaction.date_expired = expiry
transaction.save()
data = {
'endingDate': expiry.isoformat(),
'startingDate': now.isoformat(),
'maxTotalAmountOfAllPayments': '%.2f' % transaction.amount,
'maxNumberOfPayments':1,
'maxAmountPerPayment': '%.2f' % transaction.amount,
'currencyCode': transaction.currency,
'returnUrl': return_url,
'cancelUrl': cancel_url,
'requestEnvelope': { 'errorLanguage': 'en_US' },
'ipnNotificationUrl': settings.BASE_URL + reverse('PayPalIPN')
}
# Is ipnNotificationUrl being computed properly
print >> sys.stderr, 'ipnNotificationUrl', settings.BASE_URL + reverse('PayPalIPN')
self.raw_request = json.dumps(data)
self.url = "/AdaptivePayments/Preapproval"
self.headers = headers
self.connection = url_request(self)
self.code = self.connection.code()
if self.code != 200:
self.errorMessage = 'PayPal response code was %i' % self.code
return
self.raw_response = self.connection.content()
logger.info("paypal PREAPPROVAL response was: %s" % self.raw_response)
print >> sys.stderr, "paypal PREAPPROVAL response was:", self.raw_response
self.response = json.loads( self.raw_response )
logger.info(self.response)
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error Occurred"
def key( self ):
if self.response.has_key( 'preapprovalKey' ):
return self.response['preapprovalKey']
else:
@ -409,29 +578,12 @@ class Preapproval( object ):
def next_url( self ):
return '%s?cmd=_ap-preapproval&preapprovalkey=%s' % ( settings.PAYPAL_PAYMENT_HOST, self.response['preapprovalKey'] )
def error( self ):
if self.response.has_key('error'):
error = self.response['error']
logger.info(error)
return error[0]['message']
else:
return 'Paypal Preapproval: Unknown Error'
def status( self ):
if self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ):
return self.response['responseEnvelope']['ack']
else:
return None
class PreapprovalDetails(object):
class PreapprovalDetails(PaypalEnvelopeRequest):
def __init__(self, transaction):
try:
self.transaction = transaction
self.response = None
self.errorMessage = None
headers = {
'X-PAYPAL-SECURITY-USERID':settings.PAYPAL_USERNAME,
@ -446,19 +598,20 @@ class PreapprovalDetails(object):
# I think we've been tracking payKey. We might want to use our own trackingId (what's Transaction.secret for?)
data = {
'requestEnvelope': { 'errorLanguage': 'en_US' },
'preapprovalKey':transaction.reference
'preapprovalKey':transaction.preapproval_key
}
self.raw_request = json.dumps(data)
self.connection = url_request(settings.PAYPAL_ENDPOINT, "/AdaptivePayments/PreapprovalDetails", data=self.raw_request, headers=headers )
self.raw_response = self.connection.content()
self.headers = headers
self.url = "/AdaptivePayments/PreapprovalDetails"
self.connection = url_request(self)
self.code = self.connection.code()
if self.code != 200:
self.errorMessage = 'PayPal response code was %i' % self.code
return
self.raw_response = self.connection.content()
logger.info("paypal PreapprovalDetails response was: %s" % self.raw_response)
self.response = json.loads( self.raw_response )
logger.info(self.response)
@ -474,15 +627,6 @@ class PreapprovalDetails(object):
self.errorMessage = "Error: ServerError"
traceback.print_exc()
def error(self):
if self.response and self.response.has_key('error'):
error = self.response['error']
logger.info(error)
return error[0]['message']
elif self.errorMessage:
return self.errorMessage
else:
return None
class IPN( object ):
@ -532,16 +676,6 @@ class IPN( object ):
else:
return None
def key(self):
# We only keep one reference, either a prapproval key, or a pay key, for the transaction. This avoids the
# race condition that may result if the IPN for an executed pre-approval(with both a pay key and preapproval key) is received
# before we have time to store the pay key
if self.preapproval_key:
return self.preapproval_key
elif self.pay_key:
return self.pay_key
else:
return None
def success( self ):
return self.error == None

View File

@ -10,5 +10,6 @@ urlpatterns = patterns(
url(r"^paypalipn", "paypalIPN", name="PayPalIPN"),
url(r"^runtests", "runTests"),
url(r"^paymentcomplete","paymentcomplete"),
url(r"^checkstatus", "checkStatus")
url(r"^checkstatus", "checkStatus"),
url(r"^testfinish", "testFinish"),
)

View File

@ -16,8 +16,8 @@ import logging
logger = logging.getLogger(__name__)
# parameterize some test recipients
#TEST_RECEIVERS = ['jakace_1309677337_biz@gmail.com', 'seller_1317463643_biz@gmail.com']
TEST_RECEIVERS = ['glueja_1317336101_biz@gluejar.com', 'rh1_1317336251_biz@gluejar.com', 'RH2_1317336302_biz@gluejar.com']
TEST_RECEIVERS = ['jakace_1309677337_biz@gmail.com', 'seller_1317463643_biz@gmail.com']
#TEST_RECEIVERS = ['glueja_1317336101_biz@gluejar.com', 'rh1_1317336251_biz@gluejar.com', 'RH2_1317336302_biz@gluejar.com']
'''
@ -135,6 +135,26 @@ def testCancel(request):
else:
message = "Error: " + t.error
return HttpResponse(message)
'''
http://BASE/testfinish?transaction=2
Example that finishes a delayed chained transaction
'''
def testFinish(request):
if "transaction" not in request.GET.keys():
return HttpResponse("No Transaction in Request")
t = Transaction.objects.get(id=int(request.GET['transaction']))
p = PaymentManager()
if p.finish_transaction(t):
return HttpResponse("Success")
else:
message = "Error: " + t.error
return HttpResponse(message)
'''