Adding payment transaction local_status, local IPN functions, separating payment execute and finish, and updates to the amazon FPS payment system

pull/1/head
icellama21 2012-04-20 15:59:08 -04:00
parent d370091db7
commit e8b2619207
7 changed files with 470 additions and 143 deletions

View File

@ -1,7 +1,7 @@
from regluit.payment.parameters import *
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.models import Transaction
from regluit.payment.models import Transaction, PaymentResponse
from django.contrib.auth.models import User
from django.utils import simplejson as json
from django.utils.xmlutils import SimplerXMLGenerator
@ -19,6 +19,7 @@ import dateutil.parser
import hashlib
import httplib
import traceback
import datetime
import uuid
import os
import urllib
@ -43,7 +44,7 @@ AMAZON_STATUS_PAYMENT_MISMATCH = 'PE'
AMAZON_STATUS_INCOMPLETE = 'NP'
AMAZON_STATUS_NOT_REGISTERED = 'NM'
AMAZON_STATUS_CANCLLED = 'Cancelled'
AMAZON_STATUS_CANCELLED = 'Cancelled'
AMAZON_STATUS_FAILURE = 'Failure'
AMAZON_STATUS_PENDING = 'Pending'
AMAZON_STATUS_RESERVED = 'Reserved'
@ -55,8 +56,24 @@ AMAZON_IPN_STATUS_PENDING = 'PENDING'
AMAZON_IPN_STATUS_RESERVED = 'RESERVED'
AMAZON_IPN_STATUS_SUCCESS = 'SUCCESS'
def ProcessIPN(request):
'''
IPN handler for amazon
'''
try:
print "Process Amazon IPN"
except:
traceback.print_exc()
def amazonPaymentReturn(request):
'''
This is the complete view called after the co-branded API completes. It is called whenever the user
approves a preapproval or a pledge.
'''
try:
# pick up all get and post parameters and display
@ -68,7 +85,7 @@ def amazonPaymentReturn(request):
reference = request.GET['callerReference']
token = request.GET['tokenID']
status = request.GET['status']
# BUGUBG - Should we verify the signature here?
#
# Find the transaction by reference, there should only be one
@ -82,6 +99,14 @@ def amazonPaymentReturn(request):
if transaction.type == PAYMENT_TYPE_INSTANT:
# Instant payments need to be executed now
# Log the authorize transaction
r = PaymentResponse.objects.create(api="Authorize",
correlation_id = "None",
timestamp = str(datetime.datetime.now()),
info = str(request.GET),
status=status,
transaction=transaction)
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
# The above status code are unique to the return URL and are different than the pay API codes
@ -94,7 +119,17 @@ def amazonPaymentReturn(request):
#
e = Execute(transaction=transaction)
transaction.status = e.status
transaction.local_status = e.status
if e.status == AMAZON_STATUS_SUCCESS:
transaction.status = TRANSACTION_STATUS_COMPLETE_PRIMARY
elif e.status == AMAZON_STATUS_PENDING:
# Amazon leaves CC transactions pending until we get the IPN
transaction.status = TRANSACTION_STATUS_PENDING
else:
transaction.status = TRANSACTION_STATUS_ERROR
if e.success() and not e.error():
# Success case, save the ID
@ -102,26 +137,43 @@ def amazonPaymentReturn(request):
else:
print "Amazon Execute returned an error, status %s" % e.status
# Failure case
# Save some context for this transaction
# Log the pay transaction
r = PaymentResponse.objects.create(api="Pay",
correlation_id = e.correlation_id(),
timestamp = e.timestamp(),
info = e.envelope(),
status = e.status,
transaction=transaction)
else:
transaction.status = AMAZON_STATUS_FAILURE
elif transaction.type == PAYMENT_TYPE_AUTHORIZATION:
#
# Future payments, we only need to store the token. The authorization was requested with the default expidation
# Future payments, we only need to store the token. The authorization was requested with the default expiration
# date set in our settings. When we are ready, we can call execute on this
#
transaction.local_status = status
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
# The above status code are unique to the return URL and are different than the pay API codes
transaction.status = AMAZON_STATUS_PENDING
transaction.status = TRANSACTION_STATUS_ACTIVE
transaction.approved = True
transaction.pay_key = token
else:
transaction.status = AMAZON_STATUS_FAILURE
transaction.status = TRANSACTION_STATUS_ERROR
# Log the trasaction
r = PaymentResponse.objects.create(api="Authorize",
correlation_id = "None",
timestamp = str(datetime.datetime.now()),
info = str(request.GET),
status = status,
transaction=transaction)
transaction.save()
return HttpResponse("Success")
@ -155,7 +207,7 @@ class AmazonRequest:
# process the boto response if we have one. The status codes here are only boto response codes, not
# return_url codes
#
if self.status == AMAZON_STATUS_SUCCESS:
if self.status == AMAZON_STATUS_SUCCESS or self.status == AMAZON_STATUS_PENDING:
return True
else:
return False
@ -190,13 +242,21 @@ class AmazonRequest:
return None
def correlation_id(self):
return None
# The correlation ID is unique to each API call
if self.response:
return self.response.TransactionId
else:
return None
def timestamp(self):
return None
return str(datetime.datetime.now())
class Pay( AmazonRequest ):
'''
The pay function generates a redirect URL to approve the transaction
'''
def __init__( self, transaction, return_url=None, cancel_url=None, options=None, amount=None):
@ -212,7 +272,6 @@ class Pay( AmazonRequest ):
receivers = transaction.receiver_set.all()
if not amount:
# by setting primary_string of the first receiver to 'true', we are doing a Chained payment
amount = 0
for r in receivers:
amount += r.amount
@ -241,7 +300,7 @@ class Pay( AmazonRequest ):
self.errorMessage = "Error: Server Error"
def api(self):
return None
return "Amazon Co-branded PAY request"
def exec_status( self ):
return None
@ -265,6 +324,7 @@ class Preapproval(Pay):
# Call into our parent class
Pay.__init__(self, transaction, return_url=return_url, cancel_url=cancel_url, options=None, amount=amount)
class Execute(AmazonRequest):
def __init__(self, transaction=None):
@ -273,7 +333,8 @@ class Execute(AmazonRequest):
# Use the boto class top open a connection
self.connection = FPSConnection(settings.FPS_ACCESS_KEY, settings.FPS_SECRET_KEY)
self.transaction = transaction
# BUGBUG, handle multiple receivers! For now we just send the money to ourselves
self.raw_response = self.connection.pay(transaction.amount,
@ -299,4 +360,70 @@ class Execute(AmazonRequest):
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
def api(self):
return "Amazon API Pay"
def key(self):
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key
return self.transaction.pay_key
class Finish(AmazonRequest):
def __init__(self, transaction):
try:
print "Finish"
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class PaymentDetails(AmazonRequest):
def __init__(self, transaction=None):
try:
print "Payment Details"
except:
self.errorMessage = "Error: ServerError"
traceback.print_exc()
class CancelPreapproval(AmazonRequest):
def __init__(self, transaction):
try:
print "Cancel Preapproval"
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class RefundPayment(AmazonRequest):
def __init__(self, transaction):
try:
print "Refund Payment"
except:
traceback.print_exc()
self.errorMessage = "Error: Server Error"
class PreapprovalDetails(AmazonRequest):
def __init__(self, transaction):
try:
print "Preapproval Details"
except:
self.errorMessage = "Error: ServerError"
traceback.print_exc()

View File

@ -6,14 +6,12 @@ from django.conf import settings
from regluit.payment.parameters import *
if settings.PAYMENT_PROCESSOR == 'paypal':
from regluit.payment.paypal import Pay, Execute, Preapproval
from regluit.payment.paypal import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
from regluit.payment.paypal import Pay as Execute
elif settings.PAYMENT_PROCESSOR == 'amazon':
from regluit.payment.amazon import Pay, Execute, Preapproval
from regluit.payment.amazon import Pay, Execute, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
from regluit.payment.paypal import IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, IPN_PREAPPROVAL_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_NONE
from regluit.payment.paypal import 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 regluit.utils.localdatetime import now
@ -42,6 +40,11 @@ class PaymentManager( object ):
def __init__( self, embedded=False):
self.embedded = embedded
def processIPN(self, request):
# Forward to our payment processor
return ProcessIPN(request)
def update_preapproval(self, transaction):
"""Update a transaction to hold the data from a PreapprovalDetails on that transaction"""
@ -191,113 +194,6 @@ class PaymentManager( object ):
return status
def processIPN(self, 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.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.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.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()
def run_query(self, transaction_list, summary, pledged, authorized, incomplete, completed):
'''
@ -306,27 +202,27 @@ class PaymentManager( object ):
if pledged:
pledged_list = transaction_list.filter(type=PAYMENT_TYPE_INSTANT,
status=IPN_PAY_STATUS_COMPLETED)
status=TRANSACITON_STATUS_COMPLETE_PRIMARY)
else:
pledged_list = []
if authorized:
# return only ACTIVE transactions with approved=True
authorized_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PREAPPROVAL_STATUS_ACTIVE,
status=TRANSACTION_STATUS_ACTIVE,
approved=True)
else:
authorized_list = []
if incomplete:
incomplete_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PAY_STATUS_INCOMPLETE)
status=TRANSACTION_STATUS_INCOMPLETE)
else:
incomplete_list = []
if completed:
completed_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PAY_STATUS_COMPLETED)
status=TRANSACITON_STATUS_COMPLETE_PRIMARY)
else:
completed_list = []
@ -338,7 +234,7 @@ class PaymentManager( object ):
for t in pledged_list:
for r in t.receiver_set.all():
if r.status == IPN_TXN_STATUS_COMPLETED:
if r.status == TRANSACTION_STATUS_COMPLETED:
# or IPN_SENDER_STATUS_COMPLETED
# individual senders may not have been paid due to errors, and disputes/chargebacks only appear here
pledged_amount += r.amount
@ -479,7 +375,7 @@ class PaymentManager( object ):
'''
transactions = Transaction.objects.filter(campaign=campaign, status=IPN_PREAPPROVAL_STATUS_ACTIVE)
transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACITON_STATUS_ACTIVE)
for t in transactions:
result = self.cancel_transaction(t)
@ -507,7 +403,7 @@ class PaymentManager( object ):
transaction.date_executed = now()
transaction.save()
p = Execute(transaction)
p = Finish(transaction)
# Create a response for this
envelope = p.envelope()
@ -561,7 +457,7 @@ class PaymentManager( object ):
transaction.date_payment = now()
transaction.save()
p = Pay(transaction)
p = Execute(transaction)
# Create a response for this
envelope = p.envelope()
@ -725,7 +621,7 @@ class PaymentManager( object ):
# 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
if transaction.status != IPN_PREAPPROVAL_STATUS_ACTIVE:
if transaction.status != TRANSACITON_STATUS_ACTIVE:
logger.info("Error, attempt to modify a transaction that is not active")
return False, None
@ -789,7 +685,7 @@ class PaymentManager( object ):
# 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:
if transaction.status != TRANSACITON_STATUS_COMPLETE_PRIMARY:
logger.info("Refund Transaction failed, invalid transaction status")
return False

View File

@ -0,0 +1,177 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Transaction.local_status'
db.add_column('payment_transaction', 'local_status', self.gf('django.db.models.fields.CharField')(default='NONE', max_length=32, null=True), keep_default=False)
# Adding field 'PaymentResponse.status'
db.add_column('payment_paymentresponse', 'status', self.gf('django.db.models.fields.CharField')(max_length=32, null=True), keep_default=False)
# Adding field 'Receiver.local_status'
db.add_column('payment_receiver', 'local_status', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'Transaction.local_status'
db.delete_column('payment_transaction', 'local_status')
# Deleting field 'PaymentResponse.status'
db.delete_column('payment_paymentresponse', 'status')
# Deleting field 'Receiver.local_status'
db.delete_column('payment_receiver', 'local_status')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': 'True'}),
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -16,8 +16,11 @@ class Transaction(models.Model):
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
# status: constants defined in paypal.py (e.g., IPN_PREAPPROVAL_STATUS_ACTIVE, IPN_PAY_STATUS_CREATED)
status = models.CharField(max_length=32, default='NONE', null=False)
# status: general status constants defined in parameters.py
status = models.CharField(max_length=32, default='None', null=False)
# local_status: status code specific to the payment processor
local_status = models.CharField(max_length=32, default='NONE', null=True)
# 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
@ -99,6 +102,9 @@ class PaymentResponse(models.Model):
# extra info we want to store if an error occurs such as the response message
info = models.CharField(max_length=1024, null=True)
# local status specific to the api call
status = models.CharField(max_length=32, null=True)
transaction = models.ForeignKey(Transaction, null=False)
def __unicode__(self):
@ -113,6 +119,7 @@ class Receiver(models.Model):
currency = models.CharField(max_length=10)
status = models.CharField(max_length=64)
local_status = models.CharField(max_length=64, null=True)
reason = models.CharField(max_length=64)
primary = models.BooleanField()
txn_id = models.CharField(max_length=64)

View File

@ -12,6 +12,17 @@ TARGET_TYPE_CAMPAIGN = 1
TARGET_TYPE_LIST = 2
TARGET_TYPE_DONATION = 3
TRANSACTION_STATUS_NONE = 'None'
TRANSACTION_STATUS_COMPLETE = 'Complete'
TRANSACTION_STATUS_PENDING = 'Pending'
TRANSACITON_STATUS_COMPLETE_PRIMARY = 'Complete Primary'
TRANSACITON_STATUS_COMPLETE_SECONDARY = 'Complete Secondary'
TRANSACTION_STATUS_ACTIVE = 'Active'
TRANSACTION_STATUS_INCOMPLETE = 'Incomplete'
TRANSACTION_STATUS_ERROR = 'Error'
TRANSACTION_STATUS_CANCELLED = 'Cancelled'
TRANSACTION_STATUS_REFUNDED = 'Refunded'
# these two following parameters are probably extraneous since I think we will compute dynamically where to return each time.
COMPLETE_URL = '/paymentcomplete'
CANCEL_URL = '/paymentcancel'

View File

@ -1,7 +1,7 @@
from regluit.payment.parameters import *
from django.core.urlresolvers import reverse
from django.conf import settings
from regluit.payment.models import Transaction
from regluit.payment.models import Transaction, Receiver
from django.contrib.auth.models import User
from django.utils import simplejson as json
from django.utils.xmlutils import SimplerXMLGenerator
@ -105,6 +105,113 @@ IPN_REASON_CODE_CHARGEBACK_SETTLEMENT = 'Chargeback Settlement'
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
@ -358,7 +465,7 @@ class Pay( PaypalEnvelopeRequest ):
class Execute(PaypalEnvelopeRequest):
class Finish(PaypalEnvelopeRequest):
def __init__(self, transaction=None):
@ -799,4 +906,6 @@ class IPN( object ):
self.transactions.append(transdict)
logger.info(transdict)

View File

@ -75,7 +75,7 @@ def testExecute(request):
logger.info(str(t))
else:
transactions = Transaction.objects.filter(status='ACTIVE')
transactions = Transaction.objects.filter(status=TRANSACTION_STATUS_ACTIVE)
for t in transactions: