2011-09-27 12:48:11 +00:00
from regluit . payment . parameters import *
2011-10-14 17:52:17 +00:00
from django . core . urlresolvers import reverse
2011-10-06 23:10:20 +00:00
from django . conf import settings
2011-09-27 12:48:11 +00:00
from regluit . payment . models import Transaction
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
import datetime
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
2011-10-06 00:56:20 +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
2011-10-01 12:01:40 +00:00
#pay API status constants
2012-01-03 18:37:20 +00:00
# NONE' is not something the API produces but is particular to our implementation
2011-10-01 12:01:40 +00:00
IPN_PAY_STATUS_NONE = ' NONE '
2011-10-18 14:42:56 +00:00
2012-01-03 18:37:20 +00:00
#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
2011-10-01 12:01:40 +00:00
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 '
2011-10-18 14:42:56 +00:00
2011-10-18 23:27:20 +00:00
# particular to preapprovals -- may want to rename these constants to IPN_PREAPPROVAL_STATUS_*
2012-01-03 18:37:20 +00:00
# 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
2011-10-01 12:01:40 +00:00
IPN_PAY_STATUS_ACTIVE = " ACTIVE "
IPN_PAY_STATUS_CANCELED = " CANCELED "
2011-10-18 23:27:20 +00:00
IPN_PAY_STATUS_DEACTIVED = " DEACTIVED "
2011-10-18 14:42:56 +00:00
2012-01-03 18:37:20 +00:00
# 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
2011-10-01 12:01:40 +00:00
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 '
2012-01-03 18:37:20 +00:00
# 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 '
2011-11-28 23:39:06 +00:00
# adaptive payment adjusted IPN reason codes
2011-10-01 12:01:40 +00:00
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 '
2011-10-01 12:01:40 +00:00
class PaypalError ( RuntimeError ) :
pass
2011-09-27 12:48:11 +00:00
class url_request ( object ) :
2011-12-14 13:30:37 +00:00
def __init__ ( self , request ) :
2011-09-27 12:48:11 +00:00
2011-12-14 13:30:37 +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
2011-12-14 13:30:37 +00:00
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
2011-10-13 21:13:37 +00:00
2011-12-14 13:30:37 +00:00
'''
# Global values for the class
response = None
raw_response = None
errorMessage = None
raw_request = None
url = None
def ack ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' responseEnvelope ' ) and self . response [ ' responseEnvelope ' ] . has_key ( ' ack ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' responseEnvelope ' ] [ ' ack ' ]
else :
return None
2011-10-01 12:01:40 +00:00
2011-12-14 13:30:37 +00:00
def success ( self ) :
status = self . ack ( )
2011-12-21 18:49:24 +00:00
# print status
2011-12-14 13:30:37 +00:00
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
2011-12-14 13:30:37 +00:00
if message :
return True
else :
return False
def error_data ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' error ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' error ' ]
else :
return None
def error_id ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' error ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' error ' ] [ 0 ] [ ' errorId ' ]
2012-01-05 06:42:05 +00:00
else :
return None
2011-12-14 13:30:37 +00:00
def error_string ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' error ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' error ' ] [ 0 ] [ ' message ' ]
2011-10-18 14:42:56 +00:00
2011-12-14 13:30:37 +00:00
elif self . errorMessage :
return self . errorMessage
2011-10-18 14:42:56 +00:00
2011-12-14 13:30:37 +00:00
else :
return None
def envelope ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' responseEnvelope ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' responseEnvelope ' ]
else :
return None
2011-09-29 07:50:07 +00:00
2011-12-14 13:30:37 +00:00
def correlation_id ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' responseEnvelope ' ) and self . response [ ' responseEnvelope ' ] . has_key ( ' correlationId ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' responseEnvelope ' ] [ ' correlationId ' ]
else :
return None
def timestamp ( self ) :
2012-01-05 06:42:05 +00:00
if self . response and self . response . has_key ( ' responseEnvelope ' ) and self . response [ ' responseEnvelope ' ] . has_key ( ' timestamp ' ) :
2011-12-14 13:30:37 +00:00
return self . response [ ' responseEnvelope ' ] [ ' timestamp ' ]
else :
return None
class Pay ( PaypalEnvelopeRequest ) :
def __init__ ( self , transaction , return_url = None , cancel_url = None ) :
2011-10-27 06:35:46 +00:00
2011-12-14 13:30:37 +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
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
2011-12-19 23:34:30 +00:00
# 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.
2011-12-14 13:30:37 +00:00
# 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
2011-12-14 13:30:37 +00:00
# Is ipnNotificationUrl being computed properly
2011-12-21 18:49:24 +00:00
#print >> sys.stderr, 'ipnNotificationUrl', settings.BASE_URL + reverse('PayPalIPN')
2011-12-14 13:30:37 +00:00
# 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
2011-12-14 13:30:37 +00:00
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
2011-12-14 13:30:37 +00:00
self . response = json . loads ( self . raw_response )
logger . info ( self . response )
except :
traceback . print_exc ( )
self . errorMessage = " Error: Server Error "
2011-10-27 08:05:21 +00:00
2011-12-14 13:30:37 +00:00
def api ( self ) :
return self . actionType
2011-09-27 12:48:11 +00:00
2011-12-14 13:30:37 +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 ) :
2011-12-14 13:30:37 +00:00
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 ) :
2011-10-06 23:10:20 +00:00
return ' %s ?cmd=_ap-payment&paykey= %s ' % ( settings . PAYPAL_PAYMENT_HOST , self . response [ ' payKey ' ] )
2011-09-27 12:48:11 +00:00
2011-11-22 11:14:58 +00:00
def embedded_url ( self ) :
return ' %s /webapps/adaptivepayment/flow/pay?paykey= %s &expType=light ' % ( settings . PAYPAL_PAYMENT_HOST , self . response [ ' payKey ' ] )
2011-12-14 13:30:37 +00:00
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 ,
' 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 ) :
2011-10-14 17:52:17 +00:00
def __init__ ( self , transaction = None ) :
2011-10-18 14:42:56 +00:00
2011-12-06 14:36:18 +00:00
try :
self . transaction = transaction
2011-10-27 08:05:21 +00:00
2011-12-06 14:36:18 +00:00
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 )
2011-12-14 13:30:37 +00:00
self . headers = headers
self . url = " /AdaptivePayments/PaymentDetails "
self . connection = url_request ( self )
2011-12-06 14:36:18 +00:00
self . code = self . connection . code ( )
if self . code != 200 :
self . errorMessage = ' PayPal response code was %i ' % self . code
return
2011-12-14 13:30:37 +00:00
self . raw_response = self . connection . content ( )
2011-12-06 14:36:18 +00:00
logger . info ( " paypal PaymentDetails 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 . 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 )
2011-10-27 08:05:21 +00:00
2011-12-06 14:36:18 +00:00
except :
self . errorMessage = " Error: ServerError "
traceback . print_exc ( )
2011-10-27 08:05:21 +00:00
2011-10-18 14:42:56 +00:00
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
2011-12-14 13:30:37 +00:00
class CancelPreapproval ( PaypalEnvelopeRequest ) :
2011-10-01 12:01:40 +00:00
def __init__ ( self , transaction ) :
2011-12-14 13:30:37 +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 ' ,
}
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 "
2012-01-05 06:42:05 +00:00
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 "
2011-10-01 12:01:40 +00:00
2011-12-14 13:30:37 +00:00
class Preapproval ( PaypalEnvelopeRequest ) :
2011-12-20 19:56:01 +00:00
def __init__ ( self , transaction , amount , expiry = None , return_url = None , cancel_url = None ) :
2011-09-29 07:50:07 +00:00
2011-12-14 13:30:37 +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
2011-12-20 19:56:01 +00:00
# set the expiration date for the preapproval if not passed in
2011-12-14 13:30:37 +00:00
now = datetime . datetime . utcnow ( )
2011-12-20 19:56:01 +00:00
if expiry is None :
expiry = now + datetime . timedelta ( days = settings . PREAPPROVAL_PERIOD )
2011-12-14 13:30:37 +00:00
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
2011-12-21 18:49:24 +00:00
# print >> sys.stderr, 'ipnNotificationUrl', settings.BASE_URL + reverse('PayPalIPN')
2011-12-14 13:30:37 +00:00
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
2011-12-14 13:30:37 +00:00
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-11-22 11:14:58 +00:00
2011-09-29 07:50:07 +00:00
def next_url ( self ) :
2011-10-06 23:10:20 +00:00
return ' %s ?cmd=_ap-preapproval&preapprovalkey= %s ' % ( settings . PAYPAL_PAYMENT_HOST , self . response [ ' preapprovalKey ' ] )
2011-09-29 07:50:07 +00:00
2011-12-14 13:30:37 +00:00
class PreapprovalDetails ( PaypalEnvelopeRequest ) :
2011-12-06 14:36:18 +00:00
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 ' } ,
2011-12-14 13:30:37 +00:00
' preapprovalKey ' : transaction . preapproval_key
2011-12-06 14:36:18 +00:00
}
self . raw_request = json . dumps ( data )
2011-12-14 13:30:37 +00:00
self . headers = headers
self . url = " /AdaptivePayments/PreapprovalDetails "
self . connection = url_request ( self )
2011-12-06 14:36:18 +00:00
self . code = self . connection . code ( )
if self . code != 200 :
self . errorMessage = ' PayPal response code was %i ' % self . code
return
2011-12-14 13:30:37 +00:00
self . raw_response = self . connection . content ( )
2011-12-06 14:36:18 +00:00
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 )
2012-01-11 21:47:56 +00:00
# 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
2011-12-06 14:36:18 +00:00
self . expiration = self . response . get ( " endingDate " , None )
self . date = self . response . get ( " startingDate " , None )
except :
self . errorMessage = " Error: ServerError "
traceback . print_exc ( )
2011-10-14 17:52:17 +00:00
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
2011-10-06 23:10:20 +00:00
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 )
2011-10-01 12:01:40 +00:00
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 )
2011-10-01 12:01:40 +00:00
self . reason_code = request . POST . get ( ' reason_code ' , None )
2011-10-27 06:35:46 +00:00
self . trackingId = request . POST . get ( ' tracking_id ' , None )
2011-09-27 12:48:11 +00:00
2012-01-11 21:47:56 +00:00
# a bit uncertain about how well PayPal sticks to a standard case
2012-01-11 21:58:29 +00:00
approved = request . POST . get ( " approved " , ' None ' )
2012-01-11 21:47:56 +00:00
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 ( )
2011-10-27 06:35:46 +00:00
def uniqueID ( self ) :
if self . trackingId :
return self . trackingId
else :
return None
2011-10-01 12:01:40 +00:00
2011-09-27 12:48:11 +00:00
def success ( self ) :
return self . error == None
2011-10-01 12:01:40 +00:00
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 )
2011-10-06 00:56:20 +00:00
logger . info ( transdict )
2011-09-27 12:48:11 +00:00