Merge branch 'jkace' of github.com:Gluejar/regluit into payment

pull/1/head
Raymond Yee 2012-01-05 09:44:22 -05:00
commit 17f0c4986e
5 changed files with 249 additions and 12 deletions

View File

@ -4,6 +4,7 @@ from django.contrib.auth.models import User
from regluit.payment.parameters import *
from regluit.payment.paypal import Pay, Execute, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE
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
import uuid
import traceback
from datetime import datetime
@ -220,7 +221,7 @@ class PaymentManager( object ):
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)
@ -233,6 +234,23 @@ class PaymentManager( object ):
# 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:
@ -590,6 +608,7 @@ class PaymentManager( object ):
'''
t = Transaction.objects.create(amount=amount,
max_amount=amount,
type=PAYMENT_TYPE_AUTHORIZATION,
execution = EXECUTE_TYPE_CHAINED_DELAYED,
target=target,
@ -629,6 +648,121 @@ class PaymentManager( object ):
logger.info("Authorize Error: " + p.error_string())
return t, None
def modify_transaction(self, transaction, amount=None, expiry=None, return_url=None, cancel_url=None):
'''
modify
Modifies a transaction. The only type of modification allowed is to the amount and expiration date
amount: the new amount
expiry: the new expiration date, or if none the current expiration date will be used
return_url: the return URL after the preapproval(if needed)
cancel_url: the cancel url after the preapproval(if needed)
return value: True if successful, false otherwise. An optional second parameter for the forward URL if a new authorhization is needed
'''
if not amount:
logger.info("Error, no amount speicified")
return False
if transaction.type != PAYMENT_TYPE_AUTHORIZATION:
# Can only modify the amount of a preapproval for now
logger.info("Error, attempt to modify an invalid transaction type")
return False, None
if transaction.status != IPN_PAY_STATUS_ACTIVE:
# 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
logger.info("Error, attempt to modify a transaction that is not active")
return False, None
if not expiry:
# Use the old expiration date
expiry = transaction.date_expired
if amount > transaction.max_amount or expiry != transaction.date_expired:
# Increase or expiuration change, cancel and start again
self.cancel_transaction(transaction)
# Start a new authorization for the new amount
t, url = self.authorize(transaction.currency,
transaction.target,
amount,
expiry,
transaction.campaign,
transaction.list,
transaction.user,
return_url,
cancel_url,
transaction.anonymous)
if t and url:
# Need to re-direct to approve the transaction
logger.info("New authorization needed, redirectiont to url %s" % url)
return True, url
else:
# No amount change necessary
logger.info("Error, unable to start a new authorization")
return False, None
elif amount < transaction.max_amount:
# Change the amount but leave the preapproval alone
transaction.amount = amount
transaction.save()
logger.info("Updated amount of transaction to %f" % amount)
return True, None
else:
# No changes
logger.info("Error, no modifications requested")
return False, None
def refund_transaction(self, transaction):
'''
refund
Refunds a transaction. The money for the transaction may have gone to a number of places. We can only
refund money that is in our account
return value: True if successful, false otherwise
'''
# 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:
logger.info("Refund Transaction failed, invalid transaction status")
return False
p = RefundPayment(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("Refund Transaction " + str(transaction.id) + " Completed")
return True
else:
transaction.error = p.error_string()
transaction.save()
logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
return False
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None, return_url=None, cancel_url=None, anonymous=False):
'''
pledge
@ -658,7 +792,8 @@ class PaymentManager( object ):
# for chained payments, first amount is the total amount
amount = D(receiver_list[0]['amount'])
t = Transaction.objects.create(amount=amount,
t = Transaction.objects.create(amount=amount,
max_amount=amount,
type=PAYMENT_TYPE_INSTANT,
execution=EXECUTE_TYPE_CHAINED_INSTANT,
target=target,

View File

@ -21,6 +21,7 @@ class Transaction(models.Model):
# 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
max_amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
currency = models.CharField(max_length=10, default='USD', null=True)
# a unique ID that can be passed to PayPal to track a transaction

View File

@ -155,7 +155,8 @@ class PaypalEnvelopeRequest:
url = None
def ack( self ):
if self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ):
if self.response and self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ):
return self.response['responseEnvelope']['ack']
else:
return None
@ -177,17 +178,22 @@ class PaypalEnvelopeRequest:
return False
def error_data(self):
if self.response.has_key('error'):
if self.response and self.response.has_key('error'):
return self.response['error']
else:
return None
def error_id(self):
if self.response.has_key('error'):
if self.response and self.response.has_key('error'):
return self.response['error'][0]['errorId']
else:
return None
def error_string(self):
if self.response.has_key('error'):
if self.response and self.response.has_key('error'):
return self.response['error'][0]['message']
elif self.errorMessage:
@ -197,19 +203,20 @@ class PaypalEnvelopeRequest:
return None
def envelope(self):
if self.response.has_key('responseEnvelope'):
if self.response and 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'):
if self.response and 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'):
if self.response and self.response.has_key('responseEnvelope') and self.response['responseEnvelope'].has_key('timestamp'):
return self.response['responseEnvelope']['timestamp']
else:
return None
@ -532,6 +539,46 @@ class CancelPreapproval(PaypalEnvelopeRequest):
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class RefundPayment(PaypalEnvelopeRequest):
def __init__(self, transaction):
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 = {
'payKey':transaction.pay_key,
'requestEnvelope': { 'errorLanguage': 'en_US' }
}
self.raw_request = json.dumps(data)
self.headers = headers
self.url = "/AdaptivePayments/Refund"
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 Refund 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( PaypalEnvelopeRequest ):

View File

@ -12,4 +12,6 @@ urlpatterns = patterns(
url(r"^paymentcomplete","paymentcomplete"),
url(r"^checkstatus", "checkStatus"),
url(r"^testfinish", "testFinish"),
url(r"^testrefund", "testRefund"),
url(r"^testmodify", "testModify"),
)

View File

@ -16,7 +16,7 @@ import logging
logger = logging.getLogger(__name__)
# parameterize some test recipients
TEST_RECEIVERS = ['jakace_1309677337_biz@gmail.com', 'seller_1317463643_biz@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']
@ -114,8 +114,8 @@ def testAuthorize(request):
return HttpResponseRedirect(url)
else:
response = t.reference
logger.info("testAuthorize: Error " + str(t.reference))
response = t.error
logger.info("testAuthorize: Error " + str(t.error))
return HttpResponse(response)
'''
@ -136,6 +136,58 @@ def testCancel(request):
message = "Error: " + t.error
return HttpResponse(message)
'''
http://BASE/testrefund?transaction=2
Example that refunds a transaction
'''
def testRefund(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.refund_transaction(t):
return HttpResponse("Success")
else:
if t.error:
message = "Error: " + t.error
else:
message = "Error"
return HttpResponse(message)
'''
http://BASE/testmodufy?transaction=2
Example that modifies the amount of a transaction
'''
def testModify(request):
if "transaction" not in request.GET.keys():
return HttpResponse("No Transaction in Request")
if "amount" in request.GET.keys():
amount = float(request.GET['amount'])
else:
amount = 200.0
t = Transaction.objects.get(id=int(request.GET['transaction']))
p = PaymentManager()
status, url = p.modify_transaction(t, amount)
if url:
logger.info("testModify: " + url)
return HttpResponseRedirect(url)
if status:
return HttpResponse("Success")
else:
return HttpResponse("Error")
'''
http://BASE/testfinish?transaction=2