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 Stripe 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.deadline_or_now + 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_%s' % campaign.type, 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.deadline_or_now + 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 process_transaction(self, currency, amount, host=PAYMENT_HOST_NONE, campaign=None, user=None, return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None, donation=False, 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 donation: transaction is a donation ''' # set the expiry date based on the campaign deadline if campaign and campaign.deadline: expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN) else: expiry = now() + 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, donation=donation, ) t.save() # does user have enough credit to transact now? if user.is_authenticated() and user.credit.available >= amount: # YES! return_path = "{0}?{1}".format(reverse('pledge_complete'), urllib.urlencode({'tid': t.id})) return_url = urlparse.urljoin(settings.BASE_URL_SECURE, return_path) if campaign.is_pledge(): success = credit.pledge_transaction(t, user, amount) if success: pledge_created.send(sender=self, transaction=t) else: success = credit.pay_transaction(t, user, t.campaign.user_to_pay, amount) if success: t.amount = amount t.host = PAYMENT_HOST_CREDIT t.execution = EXECUTE_TYPE_INSTANT t.date_executed = now() t.status = TRANSACTION_STATUS_COMPLETE t.save() transaction_charged.send(sender=self, transaction=t) if success: return t, return_url else: # shouldn't happen logger.error('could not use credit for transaction %s' % t.id) # send user to choose payment path return t, reverse('fund', args=[t.id])