from regluit.payment.models import Transaction, Receiver, PaymentResponse, Account from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.conf import settings from regluit.payment.parameters import * from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created from regluit.payment import credit import uuid import traceback from regluit.utils.localdatetime import now from dateutil.relativedelta import relativedelta import logging from decimal import Decimal as D from xml.dom import minidom import urllib, urlparse from datetime import timedelta from django.conf import settings logger = logging.getLogger(__name__) def append_element(doc, parent, name, text): element = doc.createElement(name) parent.appendChild(element) text_node = doc.createTextNode(text) element.appendChild(text_node) return element # at this point, there is no internal context and therefore, the methods of PaymentManager can be recast into static methods class PaymentManager( object ): def processIPN(self, request, module): # Forward to our payment processor mod = __import__("regluit.payment." + module, fromlist=[str(module)]) return mod.Processor().ProcessIPN(request) def update_preapproval(self, transaction): """Update a transaction to hold the data from a PreapprovalDetails on that transaction""" t = transaction p = transaction.get_payment_class().PreapprovalDetails(t) preapproval_status = {'id':t.id, 'key':t.preapproval_key} if p.error() or not p.success(): logger.info("Error retrieving preapproval details for transaction %d" % t.id) preapproval_status["error"] = "An error occurred while verifying this transaction, see server logs for details" else: # Check the transaction status if t.status != p.status: preapproval_status["status"] = {'ours':t.status, 'theirs':p.status} t.status = p.status t.local_status = p.local_status t.save() # check the currency code if t.currency != p.currency: preapproval_status["currency"] = {'ours':t.currency, 'theirs':p.currency} t.currency = p.currency t.save() # Check the amount if t.max_amount != D(p.amount): preapproval_status["amount"] = {'ours':t.max_amount, 'theirs':p.amount} t.max_amount = p.amount t.save() # Check approved if t.approved != p.approved: preapproval_status["approved"] = {'ours':t.approved, 'theirs':p.approved} t.approved = p.approved t.save() # In amazon FPS, we may not have a pay_key via the return URL, update here try: if t.pay_key != p.pay_key: preapproval_status['pay_key'] = {'ours':t.pay_key, 'theirs':p.pay_key} t.pay_key = p.pay_key t.save() except: # No problem, p.pay_key is not defined for paypal function blah = "blah" return preapproval_status def update_payment(self, transaction): """Update a transaction to hold the data from a PaymentDetails on that transaction""" t = transaction payment_status = {'id':t.id} p = transaction.get_payment_class().PaymentDetails(t) if p.error() or not p.success(): logger.info("Error retrieving payment details for transaction %d" % t.id) payment_status['error'] = "An error occurred while verifying this transaction, see server logs for details" else: # Check the transaction status if t.status != p.status: payment_status['status'] = {'ours': t.status, 'theirs': p.status} t.status = p.status t.local_status = p.local_status t.save() receivers_status = [] for r in p.transactions: # This is only supported for paypal at this time try: receiver = Receiver.objects.get(transaction=t, email=r['email']) receiver_status = {'email':r['email']} logger.info(r) logger.info(receiver) # Check for updates on each receiver's status. Note that unprocessed delayed chained payments # will not have a status code or txn id code if receiver.status != r['status']: receiver_status['status'] = {'ours': receiver.status, 'theirs': r['status']} receiver.status = r['status'] receiver.save() if receiver.txn_id != r['txn_id']: receiver_status['txn_id'] = {'ours':receiver.txn_id, 'theirs':r['txn_id']} receiver.txn_id = r['txn_id'] receiver.save() except: traceback.print_exc() if not set(["status","txn_id"]).isdisjoint(receiver_status.keys()): receivers_status.append(receiver_status) if len(receivers_status): payment_status["receivers"] = receivers_status return payment_status def checkStatus(self, past_days=None, transactions=None): ''' Run through all pay transactions and verify that their current status is as we think. Allow for a list of transactions to be passed in or for the method to check on all transactions within the given past_days ''' DEFAULT_DAYS_TO_CHECK = 3 status = {'payments':[], 'preapprovals':[]} # look at all PAY transactions for stated number of past days; if past_days is not int, get all Transaction # only PAY transactions have date_payment not None if transactions is None: if past_days is None: past_days = DEFAULT_DAYS_TO_CHECK try: ref_date = now() - relativedelta(days=int(past_days)) payment_transactions = Transaction.objects.filter(date_payment__gte=ref_date) except: ref_date = now() payment_transactions = Transaction.objects.filter(date_payment__isnull=False) logger.info(payment_transactions) # Now look for preapprovals that have not been paid and check on their status preapproval_transactions = Transaction.objects.filter(date_authorized__gte=ref_date, date_payment=None, type=PAYMENT_TYPE_AUTHORIZATION) logger.info(preapproval_transactions) transactions = payment_transactions | preapproval_transactions for t in transactions: # deal with preapprovals if t.date_payment is None: preapproval_status = self.update_preapproval(t) logger.info("transaction: {0}, preapproval_status: {1}".format(t, preapproval_status)) if not set(['status', 'currency', 'amount', 'approved']).isdisjoint(set(preapproval_status.keys())): status["preapprovals"].append(preapproval_status) # update payments else: payment_status = self.update_payment(t) if not set(["status", "receivers"]).isdisjoint(payment_status.keys()): status["payments"].append(payment_status) # Clear out older, duplicate preapproval transactions cleared_list = [] for p in transactions: # pick out only the preapprovals if p.date_payment is None and p.type == PAYMENT_TYPE_AUTHORIZATION and p.status == TRANSACTION_STATUS_ACTIVE and p not in cleared_list: # keep only the newest transaction for this user and campaign user_transactions_for_campaign = Transaction.objects.filter(user=p.user, status=TRANSACTION_STATUS_ACTIVE, campaign=p.campaign).order_by('-date_authorized') if len(user_transactions_for_campaign) > 1: logger.info("Found %d active transactions for campaign" % len(user_transactions_for_campaign)) self.cancel_related_transaction(user_transactions_for_campaign[0], status=TRANSACTION_STATUS_ACTIVE, campaign=transactions[0].campaign) cleared_list.extend(user_transactions_for_campaign) # Note, we may need to call checkstatus again here return status def run_query(self, transaction_list, summary=True, campaign_total=False, pledged=False, authorized=False, incomplete=False, completed=False, pending=False, error=False, failed=False, **kwargs): ''' Generic query handler for returning summary and transaction info, see query_user and query_campaign campaign_total=True includes all payment types which should count towards campaign total ''' if campaign_total: # must double check when adding Paypal or other # return only ACTIVE transactions with approved=True list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, approved=True).exclude(status=TRANSACTION_STATUS_CANCELED) list = list | transaction_list.filter(type=PAYMENT_TYPE_INSTANT, status=TRANSACTION_STATUS_COMPLETE) else: list = Transaction.objects.none() if pledged: list = list | transaction_list.filter(type=PAYMENT_TYPE_INSTANT, status=TRANSACTION_STATUS_COMPLETE) if authorized: # return only ACTIVE transactions with approved=True list = list | transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, status=TRANSACTION_STATUS_ACTIVE, approved=True) if incomplete: list = list | transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, status=TRANSACTION_STATUS_INCOMPLETE) if completed: list = list | transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, status=TRANSACTION_STATUS_COMPLETE) if pending: list = list | transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, status=TRANSACTION_STATUS_PENDING) if error: list = list | transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, status=TRANSACTION_STATUS_ERROR) if failed: list = list | transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION, status=TRANSACTION_STATUS_FAILED) if summary: amount = D('0.00') for t in list: if t.type==PAYMENT_TYPE_INSTANT: for r in t.receiver_set.all(): # # Currently receivers are only used for paypal, so keep the paypal status code here # if r.status == IPN_SENDER_STATUS_COMPLETED: # or IPN_SENDER_STATUS_COMPLETED # individual senders may not have been paid due to errors, and disputes/chargebacks only appear here amount += r.amount else: amount += t.amount return amount else: return list def query_user(self, user, **kwargs): ''' query_user Returns either an amount or list of transactions for a user summary: if true, return a float of the total, if false, return a list of transactions return value: either a float summary or a list of transactions Note: this method appears to be unused. ''' transactions = Transaction.objects.filter(user=user) return self.run_query(transactions, **kwargs) def query_campaign(self, campaign, **kwargs ): ''' query_campaign Returns either an amount or list of transactions for a campaign summary: if true, return a float of the total, if false, return a list of transactions return value: either a float summary or a list of transactions ''' transactions = Transaction.objects.filter(campaign=campaign) return self.run_query(transactions, **kwargs) def execute_campaign(self, campaign): ''' execute_campaign attempts to execute all pending transactions for a campaign. return value: returns a list of transactions with the status of each receiver/transaction updated ''' # only allow active transactions to go through again, if there is an error, intervention is needed transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACTION_STATUS_ACTIVE) results = [] for t in transactions: # # Currently receivers are only used for paypal, so it is OK to leave the paypal info here # receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':t.amount}, {'email':campaign.paypal_receiver, 'amount':D(t.amount) * (D('1.00') - D(str(settings.GLUEJAR_COMMISSION)))}] try: self.execute_transaction(t, receiver_list) except Exception as e: results.append((t, e)) else: results.append((t, None)) return results def finish_campaign(self, campaign): ''' finish_campaign attempts to execute all remaining payment to non-primary receivers This is currently only supported for paypal return value: returns a list of transactions with the status of each receiver/transaction updated ''' # QUESTION: to figure out which transactions are in a state in which the payment to the primary recipient is done but not to secondary recipient # Consider two possibilities: status=IPN_PAY_STATUS_INCOMPLETE or execution = EXECUTE_TYPE_CHAINED_DELAYED # which one? Let's try the second one # only allow incomplete transactions to go through again, if there is an error, intervention is needed transactions = Transaction.objects.filter(campaign=campaign, execution=EXECUTE_TYPE_CHAINED_DELAYED) for t in transactions: result = self.finish_transaction(t) # TO DO: update campaign status return transactions def cancel_campaign(self, campaign, reason="UNSUCCESSFUL CAMPAIGN"): ''' cancel_campaign attempts to cancel active preapprovals related to the campaign return value: returns a list of transactions with the status of each receiver/transaction updated ''' transactions = Transaction.objects.filter(campaign=campaign, status=TRANSACTION_STATUS_ACTIVE) for t in transactions: result = self.cancel_transaction(t) if result: t.reason = reason t.save() return transactions def finish_transaction(self, transaction): ''' finish_transaction calls the paypal API to execute payment to non-primary receivers transaction: the transaction we want to complete ''' if transaction.execution != EXECUTE_TYPE_CHAINED_DELAYED: logger.error("FinishTransaction called with invalid execution type") return False # mark this transaction as executed transaction.date_executed = now() transaction.save() p = transaction.get_payment_class().Finish(transaction) # Create a response for this envelope = p.envelope() if envelope: correlation = p.correlation_id() timestamp = p.timestamp() r = PaymentResponse.objects.create(api=p.url, correlation_id = correlation, timestamp = timestamp, info = p.raw_response, transaction=transaction) if p.success() and not p.error(): logger.info("finish_transaction Success") return True else: transaction.error = p.error_string() transaction.save() logger.info("finish_transaction error " + p.error_string()) return False def execute_transaction(self, transaction, receiver_list): ''' execute_transaction executes a single pending transaction. transaction: the transaction object to execute receiver_list: a list of receivers for the transaction, in this format: [ {'email':'email-1', 'amount':amount1}, {'email':'email-2', 'amount':amount2} ] return value: a bool indicating the success or failure of the process. Please check the transaction status after the IPN has completed for full information ''' if len(transaction.receiver_set.all()) > 0: # we are re-submitting a transaction, wipe the old receiver list transaction.receiver_set.all().delete() transaction.create_receivers(receiver_list) # Mark as payment attempted so we will poll this periodically for status changes transaction.date_payment = now() transaction.save() p = transaction.get_payment_class().Execute(transaction) # Create a response for this envelope = p.envelope() if envelope: correlation = p.correlation_id() timestamp = p.timestamp() r = PaymentResponse.objects.create(api=p.api(), correlation_id = correlation, timestamp = timestamp, info = p.raw_response, transaction=transaction) # We will update our transaction status when we receive the IPN if p.success() and not p.error(): transaction.pay_key = p.key() transaction.save() return True else: transaction.error = p.error_string() transaction.save() logger.info("execute_transaction Error: " + p.error_string()) return False def cancel_transaction(self, transaction): ''' cancel cancels a pre-approved transaction return value: True if successful, false otherwise ''' # does this transaction explicity require preapprovals? requires_explicit_preapprovals = transaction.get_payment_class().requires_explicit_preapprovals if requires_explicit_preapprovals: p = transaction.get_payment_class().CancelPreapproval(transaction) # Create a response for this envelope = p.envelope() if envelope: correlation = p.correlation_id() timestamp = p.timestamp() r = PaymentResponse.objects.create(api=p.url, correlation_id = correlation, timestamp = timestamp, info = p.raw_response, transaction=transaction) if p.success() and not p.error(): logger.info("Cancel Transaction " + str(transaction.id) + " Completed") return True else: transaction.error = p.error_string() transaction.save() logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string()) return False else: # if no explicit preapproval required, we just have to mark the transaction as cancelled. transaction.status = TRANSACTION_STATUS_CANCELED transaction.save() return True def authorize(self, transaction, expiry= None, return_url=None, paymentReason="unglue.it Pledge", modification=False): ''' authorize authorizes a set amount of money to be collected at a later date return_url: url to redirect supporter to after a successful transaction paymentReason: a memo line that will show up in the unglue.it accounting modification: whether this authorize call is part of a modification of an existing pledge return value: a tuple of the new transaction object and a re-direct url. If the process fails, the redirect url will be None ''' if transaction.host == PAYMENT_HOST_NONE: #TODO send user to select a payment processor -- for now, set to a system setting transaction.host = settings.PAYMENT_PROCESSOR # we might want to not allow for a return_url to be passed in but calculated # here because we have immediate access to the Transaction object. if return_url is None: return_path = "{0}?{1}".format(reverse('pledge_complete'), urllib.urlencode({'tid':transaction.id})) return_url = urlparse.urljoin(settings.BASE_URL_SECURE, return_path) p = transaction.get_payment_class().Preapproval(transaction, transaction.max_amount, expiry, return_url=return_url, paymentReason=paymentReason) # Create a response for this envelope = p.envelope() if envelope: r = PaymentResponse.objects.create(api=p.url, correlation_id = p.correlation_id(), timestamp = p.timestamp(), info = p.raw_response, transaction=transaction) if p.success() and not p.error(): transaction.preapproval_key = p.key() transaction.save() # it make sense for the payment processor library to calculate next_url when # user is redirected there. But if no redirection is required, send user # straight on to the return_url url = p.next_url() if url is None: url = return_url logger.info("Authorize Success: " + url if url is not None else '') # modification and initial pledge use different notification templates -- # decide which to send # we need to fire notifications at the first point at which we are sure # that the transaction has successfully completed; triggering notifications # when the transaction is initiated risks sending notifications on transactions # that for whatever reason fail. will need other housekeeping to handle those. # sadly this point is not yet late enough in the process -- needs to be moved # until after we are certain. if not modification: # BUGBUG: # send the notice here for now # this is actually premature since we're only about to send the user off to the payment system to # authorize a charge pledge_created.send(sender=self, transaction=transaction) return transaction, url else: transaction.error = p.error_string() transaction.save() logger.info("Authorize Error: " + p.error_string()) return transaction, None def charge(self, transaction, return_url=None, paymentReason="unglue.it Purchase"): ''' charge immediately attempt to collect on transaction return_url: url to redirect supporter to after a successful transaction paymentReason: a memo line that will show up in our stripe accounting return value: a tuple of the new transaction object and a re-direct url. If the process fails, the redirect url will be None ''' if transaction.host == PAYMENT_HOST_NONE: #TODO send user to select a payment processor -- for now, set to a system setting transaction.host = settings.PAYMENT_PROCESSOR # we might want to not allow for a return_url to be passed in but calculated # here because we have immediate access to the Transaction object. if return_url is None: #return_path = "{0}?{1}".format(reverse('charge_complete'), # urllib.urlencode({'tid':transaction.id})) return_path = "{0}?{1}".format(reverse('pledge_complete'), urllib.urlencode({'tid':transaction.id})) return_url = urlparse.urljoin(settings.BASE_URL_SECURE, return_path) p = transaction.get_payment_class().Pay(transaction, transaction.max_amount, expiry, return_url=return_url, paymentReason=paymentReason) # Create a response for this envelope = p.envelope() if envelope: r = PaymentResponse.objects.create(api=p.url, correlation_id = p.correlation_id(), timestamp = p.timestamp(), info = p.raw_response, transaction=transaction) if p.success() and not p.error(): transaction.preapproval_key = p.key() transaction.save() # it make sense for the payment processor library to calculate next_url when # user is redirected there. But if no redirection is required, send user # straight on to the return_url url = p.next_url() if url is None: url = return_url logger.info("Authorize Success: " + url if url is not None else '') return transaction, url else: transaction.error = p.error_string() transaction.save() logger.info("Authorize Error: " + p.error_string()) return transaction, None def process_transaction(self, currency, amount, host=PAYMENT_HOST_NONE, campaign=None, user=None, return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None, modification=False): ''' process saves and processes a proposed transaction; decides if the transaction should be processed immediately. currency: a 3-letter currency code, i.e. USD amount: the amount to authorize host: the name of the processing module; if none, send user back to decide! campaign: required campaign object user: optional user object return_url: url to redirect supporter to after a successful transaction paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account modification: whether this authorize call is part of a modification of an existing pledge pledge_extra: extra pledge stuff return value: a tuple of the new transaction object and a re-direct url. If the process fails, the redirect url will be None ''' # set the expiry date based on the campaign deadline expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) t = Transaction.create(amount=0, host = host, max_amount=amount, currency=currency, campaign=campaign, user=user, pledge_extra=pledge_extra ) t.save() # does user have enough credit to pledge now? if user.credit.available >= amount : # YES! credit.pledge_transaction(t,user,amount) return_path = "{0}?{1}".format(reverse('pledge_complete'), urllib.urlencode({'tid':t.id})) return_url = urlparse.urljoin(settings.BASE_URL_SECURE, return_path) pledge_created.send(sender=self, transaction=t) return t, return_url else: # send user to choose payment path return t, reverse('fund_pledge', args=[t.id]) def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None): ''' Cancels any other similar status transactions for the same campaign. Used with modify code Returns the number of transactions successfully canceled ''' related_transactions = Transaction.objects.filter(status=status, user=transaction.user) if len(related_transactions) == 0: return 0 if campaign: related_transactions = related_transactions.filter(campaign=campaign) canceled = 0 for t in related_transactions: if t.id == transaction.id: # keep our transaction continue if self.cancel_transaction(t): canceled = canceled + 1 # send notice about modification of transaction if transaction.amount > t.amount: # this should be the only one that happens up_or_down = "increased" elif transaction.amount < t.amount: # we shouldn't expect any case in which this happens up_or_down = "decreased" else: # we shouldn't expect any case in which this happens up_or_down = None pledge_modified.send(sender=self, transaction=transaction, up_or_down=up_or_down) else: logger.error("Failed to cancel transaction {0} for related transaction {1} ".format(t, transaction)) return canceled def modify_transaction(self, transaction, amount, expiry=None, pledge_extra=None, return_url=None, nevermind_url=None, paymentReason=None): ''' modify Modifies a transaction. 2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again if new amount is greater than max_amount...need to go out and get new approval. to start with, we can use the standard pledge_complete, pledge_cancel machinery might have to modify the pledge_complete, pledge_cancel because the messages are going to be different because we're modifying a pledge rather than a new one. amount: the new amount expiry: the new expiration date, or if none the current expiration date will be used return_url: the return URL after the preapproval(if needed) paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed ''' logger.info("transaction.id: {0}, amount:{1}".format(transaction.id, amount)) if amount < transaction.amount: up_or_down = "decreased" elif amount > transaction.amount: up_or_down = "increased" else: up_or_down = "modified" # if expiry is None, use the existing value if expiry is None: expiry = transaction.date_expired # does this transaction explicity require preapprovals? requires_explicit_preapprovals = transaction.get_payment_class().requires_explicit_preapprovals if transaction.type != PAYMENT_TYPE_AUTHORIZATION: logger.info("Error, attempt to modify an invalid transaction type") return False, None # 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 != TRANSACTION_STATUS_ACTIVE: logger.info("Error, attempt to modify a transaction that is not active") return False, None if transaction.host == PAYMENT_HOST_CREDIT: # does user have enough credit to pledge now? if transaction.user.credit.available >= amount-transaction.amount : # YES! transaction.set_pledge_extra(pledge_extra) credit.pledge_transaction(transaction,transaction.user,amount) return_path = "{0}?{1}".format(reverse('pledge_complete'), urllib.urlencode({'tid':transaction.id})) return_url = urlparse.urljoin(settings.BASE_URL_SECURE, return_path) logger.info("Updated amount of transaction to %f" % amount) pledge_modified.send(sender=self, transaction=transaction,up_or_down=up_or_down) return transaction, return_url else: # cancel old transaction, send user to choose new payment path # set the expiry date based on the campaign deadline expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) t = Transaction.create(amount=0, max_amount=amount, currency=transaction.currency, status=TRANSACTION_STATUS_MODIFIED, campaign=transaction.campaign, user=transaction.user, pledge_extra=pledge_extra ) t.save() credit.Processor.CancelPreapproval(transaction) return t, reverse('fund_pledge', args=[t.id]) elif requires_explicit_preapprovals and (amount > transaction.max_amount or expiry != transaction.date_expired): # set the expiry date based on the campaign deadline expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN ) # Start a new transaction for the new amount t = Transaction.create(amount=amount, max_amount=amount, host=transaction.host, currency=transaction.currency, status=TRANSACTION_STATUS_CREATED, campaign=transaction.campaign, user=transaction.user, pledge_extra=pledge_extra ) t.save() t, url = self.authorize(transaction, expiry=expiry if expiry else transaction.date_expired, return_url=return_url, paymentReason=paymentReason, modification=True ) if t and url: # Need to re-direct to approve the transaction logger.info("New authorization needed, redirection to url %s" % url) # Do not cancel the transaction here, wait until we get confirmation that the transaction is complete # then cancel all other active transactions for this campaign #self.cancel_transaction(transaction) # while it would seem to make sense to send a pledge notification change here # if we do, we will also send notifications when we initiate but do not # successfully complete a pledge modification return True, url else: # a problem in authorize logger.info("Error, unable to start a new authorization") # should we send a pledge_modified signal with state="failed" and a # corresponding notification to the user? that would go here. return False, None elif (requires_explicit_preapprovals and amount <= transaction.max_amount) or (not requires_explicit_preapprovals): # Update transaction but leave the preapproval alone transaction.amount = amount transaction.set_pledge_extra(pledge_extra) transaction.save() logger.info("Updated amount of transaction to %f" % amount) # when modifying pledges happens immediately and only within our # db, we don't have to wait until we hear back to be assured of # success; send the notification immediately pledge_modified.send(sender=self, transaction=transaction, up_or_down=up_or_down) return True, None else: # this shouldn't happen return False, None def refund_transaction(self, transaction): ''' refund Refunds a transaction. The money for the transaction may have gone to a number of places. We can only refund money that is in our account return value: True if successful, false otherwise ''' # 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 != TRANSACTION_STATUS_COMPLETE: logger.info("Refund Transaction failed, invalid transaction status") return False p = transaction.get_payment_class().RefundPayment(transaction) # Create a response for this envelope = p.envelope() if envelope: correlation = p.correlation_id() timestamp = p.timestamp() r = PaymentResponse.objects.create(api=p.url, correlation_id = correlation, timestamp = timestamp, info = p.raw_response, transaction=transaction) if p.success() and not p.error(): logger.info("Refund Transaction " + str(transaction.id) + " Completed") return True else: transaction.error = p.error_string() transaction.save() logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string()) return False def make_account(self, user, host, token=None): """delegate to a specific payment module the task of creating a payment account""" mod = __import__("regluit.payment." + host, fromlist=[host]) return mod.Processor().make_account(user=user, token=token) def retrieve_accounts(self, user, host, include_deactivated=False): """return any accounts that match user, host -- only active ones by default""" if include_deactivated: return Account.objects.filter(user=user, host=host) else: return Account.objects.filter(user=user, host=host, date_deactivated__isnull=True)