regluit/payment/paypal.py

914 lines
35 KiB
Python
Raw Normal View History

2011-09-27 12:48:11 +00:00
from regluit.payment.parameters import *
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.models import Transaction, Receiver
2011-09-27 12:48:11 +00:00
from django.contrib.auth.models import User
from django.utils import simplejson as json
from django.utils.xmlutils import SimplerXMLGenerator
from django.db import IntegrityError
from django.db.models.query_utils import Q
from django.shortcuts import render_to_response
from django.template import RequestContext
from datetime import timedelta
from regluit.utils.localdatetime import now, zuluformat
import dateutil
2011-09-27 12:48:11 +00:00
import dateutil.parser
import hashlib
import httplib
import traceback
import uuid
import os
import urllib
import urllib2
import logging
import random
import commands
import smtplib
import urlparse
import decimal
2011-11-23 17:12:37 +00:00
import sys
2011-09-27 12:48:11 +00:00
logger = logging.getLogger(__name__)
2011-09-27 12:48:11 +00:00
# transaction_type constants
IPN_TYPE_PAYMENT = 'Adaptive Payment PAY'
IPN_TYPE_ADJUSTMENT = 'Adjustment'
2011-09-29 07:50:07 +00:00
IPN_TYPE_PREAPPROVAL = 'Adaptive Payment PREAPPROVAL'
2011-09-27 12:48:11 +00:00
#pay API status constants
# NONE' is not something the API produces but is particular to our implementation
IPN_PAY_STATUS_NONE = 'NONE'
#The following apply to INSTANT PAYMENTS
#https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_APPayAPI
#CREATED - The payment request was received; funds will be transferred once the payment is approved
#COMPLETED - The payment was successful
#INCOMPLETE - Some transfers succeeded and some failed for a parallel payment or, for a delayed chained payment, secondary receivers have not been paid
#ERROR - The payment failed and all attempted transfers failed or all completed transfers were successfully reversed
#REVERSALERROR - One or more transfers failed when attempting to reverse a payment
#PROCESSING - The payment is in progress
#PENDING - The payment is awaiting processing
IPN_PAY_STATUS_CREATED = 'CREATED'
IPN_PAY_STATUS_COMPLETED = 'COMPLETED'
IPN_PAY_STATUS_INCOMPLETE = 'INCOMPLETE'
IPN_PAY_STATUS_ERROR = 'ERROR'
IPN_PAY_STATUS_REVERSALERROR = 'REVERSALERROR'
IPN_PAY_STATUS_PROCESSING = 'PROCESSING'
IPN_PAY_STATUS_PENDING = 'PENDING'
# particular to preapprovals
# https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_APPreapprovalDetails
#ACTIVE - The preapproval is active
#CANCELED - The preapproval was explicitly canceled by the sender or by PayPal
#DEACTIVED - The preapproval is not active; you can be reactivate it by resetting the personal identification number (PIN) or by contacting PayPal
IPN_PREAPPROVAL_STATUS_ACTIVE = "ACTIVE"
IPN_PREAPPROVAL_STATUS_CANCELED = "CANCELED"
IPN_PREAPPROVAL_STATUS_DEACTIVED = "DEACTIVED"
# https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_APIPN
#COMPLETED - The sender's transaction has completed
#PENDING - The transaction is awaiting further processing
#CREATED - The payment request was received; funds will be transferred once approval is received
#PARTIALLY_REFUNDED - Transaction was partially refunded
#DENIED - The transaction was rejected by the receiver
#PROCESSING - The transaction is in progress
#REVERSED - The payment was returned to the sender
#REFUNDED - The payment was refunded
#FAILED - The payment failed
IPN_SENDER_STATUS_COMPLETED = 'COMPLETED'
IPN_SENDER_STATUS_PENDING = 'PENDING'
IPN_SENDER_STATUS_CREATED = 'CREATED'
IPN_SENDER_STATUS_PARTIALLY_REFUNDED = 'PARTIALLY_REFUNDED'
IPN_SENDER_STATUS_DENIED = 'DENIED'
IPN_SENDER_STATUS_PROCESSING = 'PROCESSING'
IPN_SENDER_STATUS_REVERSED = 'REVERSED'
IPN_SENDER_STATUS_REFUNDED = 'REFUNDED'
IPN_SENDER_STATUS_FAILED = 'FAILED'
2011-09-27 12:48:11 +00:00
# action_type constants
IPN_ACTION_TYPE_PAY = 'PAY'
IPN_ACTION_TYPE_CREATE = 'CREATE'
# individual sender transaction constants (???)
2011-09-27 12:48:11 +00:00
IPN_TXN_STATUS_COMPLETED = 'Completed'
IPN_TXN_STATUS_PENDING = 'Pending'
IPN_TXN_STATUS_REFUNDED = 'Refunded'
# adaptive payment adjusted IPN reason codes
IPN_REASON_CODE_CHARGEBACK_SETTLEMENT = 'Chargeback Settlement'
2011-09-27 12:48:11 +00:00
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
2011-09-27 12:48:11 +00:00
class url_request( object ):
def __init__( self, request):
2011-09-27 12:48:11 +00:00
conn = httplib.HTTPSConnection(settings.PAYPAL_ENDPOINT)
conn.request("POST", request.url, request.raw_request, request.headers)
2011-09-27 12:48:11 +00:00
#Check the response - should be 200 OK.
self.response = conn.getresponse()
def content( self ):
return self.response.read()
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.
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 and 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()
2011-12-21 18:49:24 +00:00
# print status
if status == "Success" or status == "SuccessWithWarning":
return True
else:
return False
def error(self):
message = self.errorMessage
2011-12-21 18:49:24 +00:00
# print message
if message:
return True
else:
return False
def error_data(self):
if self.response and self.response.has_key('error'):
return self.response['error']
else:
return None
def error_id(self):
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 and 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 and self.response.has_key('responseEnvelope'):
return self.response['responseEnvelope']
else:
return None
2011-09-29 07:50:07 +00:00
def correlation_id(self):
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 and 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):
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
# The PayPal documentation says that fees for delayed chain payments cannot be set to secondaryonly
# but the sandbox seems to show the secondary recipient paying all the fees.
# 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)
2011-12-21 18:49:24 +00:00
#print >> sys.stderr, "paypal PAY data:", data
# Is ipnNotificationUrl being computed properly
2011-12-21 18:49:24 +00:00
#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()
2011-12-21 18:49:24 +00:00
#print >> sys.stderr, "PAY request", settings.PAYPAL_ENDPOINT, "/AdaptivePayments/Pay", self.raw_request, headers
logger.info("paypal PAY response was: %s" % self.raw_response)
2011-12-21 18:49:24 +00:00
#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
2011-09-27 12:48:11 +00:00
def exec_status( self ):
2011-09-27 12:48:11 +00:00
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
2011-09-27 12:48:11 +00:00
def next_url( self ):
return '%s?cmd=_ap-payment&paykey=%s' % (settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] )
2011-09-27 12:48:11 +00:00
def embedded_url(self):
return '%s/webapps/adaptivepayment/flow/pay?paykey=%s&expType=light' % ( settings.PAYPAL_PAYMENT_HOST, self.response['payKey'] )
class Finish(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,
'X-PAYPAL-APPLICATION-ID':settings.PAYPAL_APPID,
'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"
class PaymentDetails(PaypalEnvelopeRequest):
def __init__(self, transaction=None):
try:
self.transaction = 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'
}
# we can feed any of payKey, transactionId, and trackingId to identify transaction in question
# 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' },
'trackingId':transaction.secret
}
self.raw_request = json.dumps(data)
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 )
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)
payment_info = payment_info_list.get("paymentInfo", None)
self.transactions = []
for payment in payment_info:
receiver = {}
receiver['status'] = payment.get("transactionStatus", None)
receiver['txn_id'] = payment.get("transactionId")
r = payment.get("receiver", None)
if r:
receiver['email'] = r.get('email')
self.transactions.append(receiver)
except:
self.errorMessage = "Error: ServerError"
traceback.print_exc()
def compare(self):
"""compare current status information from what's in the current transaction object"""
# I don't think we do anything with fundingtypeList, memo
# status can be:
# transaction.type should be PAYMENT_TYPE_INSTANT
# actionType can be: 'PAY', 'CREATE', 'PAY_PRIMARY' -- I think we are doing only 'PAY' right now
comp = [(self.transaction.status, self.response.get('status')),
(self.transaction.type, self.response.get('actionType')),
(self.transaction.currency, self.response.get('currencyCode')),
('EACHRECEIVER' if len(self.transaction.receiver_set.all()) == 1 else 'SECONDARYONLY',self.response.get('feesPayer')),
(self.transaction.reference, self.response.get('payKey')), # payKey supposedly expires after 3 hours
('false', self.response.get('reverseAllParallelPaymentsOnError')),
(None, self.response.get('sender'))
]
# loop through recipients
return comp
# also getting sender / senderEmail info too here that we don't currently hold in transaction. Want to save? Does that info come in IPN?
# responseEnvelope
# reverseAllParallelPaymentsOnError
# self.response.get('responseEnvelope')['ack'] should be 'Success' Can also be 'Failure', 'Warning', 'SuccessWithWarning', 'FailureWithWarning'
# can possibly use self.response.get('responseEnvelope')['timestamp'] to update self.transaction.date_modified
# preapprovalKey -- self.transaction doesn't hold that info right now
# paymentInfoList -- holds info for each recipient
2011-09-27 12:48:11 +00:00
class CancelPreapproval(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 = {
'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 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 ):
def __init__( self, transaction, amount, expiry=None, return_url=None, cancel_url=None):
2011-09-29 07:50:07 +00:00
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 if not passed in
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()
data = {
'endingDate': zuluformat(expiry),
'startingDate': zuluformat(now_val),
'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
2011-12-21 18:49:24 +00:00
# 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)
2011-12-21 18:49:24 +00:00
# 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 ):
2011-09-29 07:50:07 +00:00
if self.response.has_key( 'preapprovalKey' ):
return self.response['preapprovalKey']
else:
return None
2011-09-29 07:50:07 +00:00
def next_url( self ):
return '%s?cmd=_ap-preapproval&preapprovalkey=%s' % ( settings.PAYPAL_PAYMENT_HOST, self.response['preapprovalKey'] )
2011-09-29 07:50:07 +00:00
class PreapprovalDetails(PaypalEnvelopeRequest):
def __init__(self, transaction):
try:
self.transaction = 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'
}
# we can feed any of payKey, transactionId, and trackingId to identify transaction in question
# 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.preapproval_key
}
self.raw_request = json.dumps(data)
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)
self.status = self.response.get("status", None)
self.amount = self.response.get("maxTotalAmountOfAllPayments", None)
self.currency = self.response.get("currencyCode", None)
# a bit uncertain about how well PayPal sticks to a standard case
approved = self.response.get("approved", 'None')
if approved.lower() == 'true':
self.approved = True
elif approved.lower() == 'false':
self.approved = False
else:
self.approved = None
try:
self.expiration = dateutil.parser.parse(self.response.get("endingDate"))
except:
self.expiration = None
try:
self.date = dateutil.parser.parse(self.response.get("startingDate", None))
except:
self.date = None
except:
self.errorMessage = "Error: ServerError"
traceback.print_exc()
2011-09-27 12:48:11 +00:00
class IPN( object ):
def __init__( self, request ):
try:
# verify that the request is paypal's
self.error = None
url = "%s?cmd=_notify-validate" % settings.PAYPAL_PAYMENT_HOST
2011-09-27 12:48:11 +00:00
data=urllib.urlencode(request.POST.copy())
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
raw_response = response.read()
status = response.code
# check code
if status != 200:
self.error = 'PayPal response code was %i' % verify_response.code()
return
# check response
if raw_response != 'VERIFIED':
self.error = 'PayPal response was "%s"' % raw_response
return
# Process the details
self.status = request.POST.get('status', None)
self.sender_email = request.POST.get('sender_email', None)
self.action_type = request.POST.get('action_type', None)
self.pay_key = request.POST.get('pay_key', None)
2011-09-29 07:50:07 +00:00
self.preapproval_key = request.POST.get('preapproval_key', None)
2011-09-27 12:48:11 +00:00
self.transaction_type = request.POST.get('transaction_type', None)
self.reason_code = request.POST.get('reason_code', None)
self.trackingId = request.POST.get('tracking_id', None)
2011-09-27 12:48:11 +00:00
# a bit uncertain about how well PayPal sticks to a standard case
approved = request.POST.get("approved", 'None')
if approved.lower() == 'true':
self.approved = True
elif approved.lower() == 'false':
self.approved = False
else:
self.approved = None
2011-09-27 12:48:11 +00:00
self.process_transactions(request)
except:
self.error = "Error: ServerError"
traceback.print_exc()
def uniqueID(self):
if self.trackingId:
return self.trackingId
else:
return None
2011-09-27 12:48:11 +00:00
def success( self ):
return self.error == None
2011-09-27 12:48:11 +00:00
@classmethod
def slicedict(cls, d, s):
return dict((str(k.replace(s, '', 1)), v) for k,v in d.iteritems() if k.startswith(s))
def process_transactions(self, request):
self.transactions = []
transaction_nums = range(6)
for transaction_num in transaction_nums:
transdict = IPN.slicedict(request.POST, 'transaction[%s].' % transaction_num)
if len(transdict) > 0:
self.transactions.append(transdict)
logger.info(transdict)
2011-09-27 12:48:11 +00:00
2011-09-27 12:48:11 +00:00