from regluit.payment.parameters import * from django.core.urlresolvers import reverse from django.conf import settings 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 import sys logger = logging.getLogger(__name__) # transaction_type constants IPN_TYPE_PAYMENT = 'Adaptive Payment PAY' IPN_TYPE_ADJUSTMENT = 'Adjustment' IPN_TYPE_PREAPPROVAL = 'Adaptive Payment PREAPPROVAL' #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 -- may want to rename these constants to IPN_PREAPPROVAL_STATUS_* # 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_PAY_STATUS_ACTIVE = "ACTIVE" IPN_PAY_STATUS_CANCELED = "CANCELED" IPN_PAY_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' # action_type constants IPN_ACTION_TYPE_PAY = 'PAY' IPN_ACTION_TYPE_CREATE = 'CREATE' # individual sender transaction constants (???) 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' IPN_REASON_CODE_ADMIN_REVERSAL = 'Admin reversal' IPN_REASON_CODE_REFUND = 'Refund' class PaypalError(RuntimeError): pass class url_request( object ): def __init__( self, request): 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() 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() # 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 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 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) #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, '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.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 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): 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 = datetime.datetime.utcnow() if expiry is None: expiry = now + datetime.timedelta( days=settings.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: return None def next_url( self ): return '%s?cmd=_ap-preapproval&preapprovalkey=%s' % ( settings.PAYPAL_PAYMENT_HOST, self.response['preapprovalKey'] ) 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 self.expiration = self.response.get("endingDate", None) self.date = self.response.get("startingDate", None) except: self.errorMessage = "Error: ServerError" traceback.print_exc() 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 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) self.preapproval_key = request.POST.get('preapproval_key', None) 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) # 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 self.process_transactions(request) except: self.error = "Error: ServerError" traceback.print_exc() def uniqueID(self): if self.trackingId: return self.trackingId else: return None def success( self ): return self.error == None @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)