Adding payment transaction local_status, local IPN functions, separating payment execute and finish, and updates to the amazon FPS payment system
parent
d370091db7
commit
e8b2619207
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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']
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
Loading…
Reference in New Issue