def notify_ending_soon(): c_active = Campaign.objects.filter(status='Active', type=REWARDS) for c in c_active: if c.deadline - now() < timedelta( 7) and c.deadline - now() >= timedelta(6): """ if the campaign is still active and there's only a week left until it closes, send reminder notification """ deadline_impending.send(sender=None, campaign=c)
def clean_deadline(self): if self.instance.type in {BUY2UNGLUE, THANKS} : return None new_deadline_date = self.cleaned_data['deadline'] new_deadline = new_deadline_date + timedelta(hours=23, minutes=59) if self.instance: if self.instance.status == 'ACTIVE': return self.instance.deadline if new_deadline_date - now() > timedelta(days=int(settings.UNGLUEIT_LONGEST_DEADLINE)): raise forms.ValidationError(_('The chosen closing date is more than %s days from now' % settings.UNGLUEIT_LONGEST_DEADLINE)) elif new_deadline - now() < timedelta(days=0): raise forms.ValidationError(_('The chosen closing date is in the past')) return new_deadline
def testSimple(self): """ create a single transaction with PAYMENT_TYPE_AUTHORIZATION / ACTIVE with a $12.34 pledge and see whether the payment manager can query and get the right amount. """ user = User.objects.create_user('payment_test', '*****@*****.**', 'payment_test') w = Work() w.save() c = Campaign(target=D('1000.00'),deadline=now() + timedelta(days=180),work=w) c.save() t = Transaction() t.amount = D('12.34') t.type = PAYMENT_TYPE_AUTHORIZATION t.status = 'ACTIVE' t.approved = True t.campaign = c t.user = user t.save() #test pledge adders user.profile.reset_pledge_badge() self.assertEqual(user.profile.badges.all()[0].name,'pledger') p = PaymentManager() results = p.query_campaign(c,campaign_total=True, summary=False) self.assertEqual(results[0].amount, D('12.34')) self.assertEqual(c.left,c.target-D('12.34')) self.assertEqual(c.supporters_count, 1)
def handle(self, *args, **options): for work in Work.objects.all(): if work.campaigns.all().count() > 0: continue campaign = Campaign() campaign.name = work.title campaign.work = work campaign.description = "Test Campaign" # random campaign target between $200 and $10,000 campaign.target = D(randint(200, 10000)) # add a test rightsholder recipient right now campaign.paypal_receiver = settings.PAYPAL_TEST_RH_EMAIL # random deadline between 5 days from now and 180 days from now _now = now() campaign.deadline = random_date(_now + timedelta(days=5), _now + timedelta(days=180)) # randomly activate some of the campaigns coinflip = D(randint(0, 10)) if coinflip > 5: campaign.activate() campaign.save() print "campaign %s...status: %s" % (unicode(campaign).encode( 'ascii', 'replace'), campaign.status)
def clean_cc_date_initial(self): new_cc_date_initial = self.cleaned_data['cc_date_initial'] if new_cc_date_initial.date() > settings.MAX_CC_DATE: raise forms.ValidationError('The initial Ungluing Date cannot be after %s'%settings.MAX_CC_DATE) elif new_cc_date_initial - now() < timedelta(days=0): raise forms.ValidationError('The initial Ungluing date must be in the future!') return new_cc_date_initial
def next_acq(self): """ This is the next available copy in the user's libraries""" loans = self.acqs.filter(license=LIBRARY, refreshes__gt=now()).order_by('refreshes') if loans.count() == 0: return None else: return loans[0]
def set_credit_approved(self, amount): self.amount = amount self.host = PAYMENT_HOST_CREDIT self.type = PAYMENT_TYPE_AUTHORIZATION self.status = TRANSACTION_STATUS_ACTIVE self.approved = True now_val = now() self.date_authorized = now_val self.date_expired = now_val + datetime.timedelta( days=settings.PREAPPROVAL_PERIOD) self.save() pledge_created.send(sender=self, transaction=self)
def notify_unclaimed_gifts(): unclaimed = Gift.objects.filter(used=None) for gift in unclaimed: """ send notice every 7 days """ unclaimed_duration = (now() - gift.acq.created).days if unclaimed_duration > 0 and unclaimed_duration % 7 == 0: # first notice in 7 days notification.send_now([gift.acq.user], "purchase_gift_waiting", {'gift': gift}, True) notification.send_now([gift.giver], "purchase_notgot_gift", {'gift': gift}, True)
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 __init__( self, transaction, return_url=None, amount=None, paymentReason="", token=None): self.transaction=transaction self.url = return_url now_val = now() transaction.date_authorized = now_val # ASSUMPTION: a user has any given moment one and only one active payment Account if token: # user is anonymous account = transaction.get_payment_class().make_account(token = token, email = transaction.receipt) else: account = transaction.user.profile.account if not account: logger.warning("user {0} has no active payment account".format(transaction.user)) raise StripelibError("user {0} has no active payment account".format(transaction.user)) logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id)) # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE # should approved be set to False and wait for a webhook? transaction.approved = True transaction.type = PAYMENT_TYPE_INSTANT transaction.host = PAYMENT_HOST_STRIPE transaction.preapproval_key = account.account_id transaction.currency = 'USD' transaction.amount = amount transaction.save() # execute the transaction p = transaction.get_payment_class().Execute(transaction) if p.success() and not p.error(): transaction.pay_key = p.key() transaction.save() else: self.errorMessage = p.errorMessage #pass error message up logger.info("execute_transaction Error: " + p.error_string())
def test_paymentmanager_charge(self): """ test regluit.payment.manager.PaymentManager.charge trying to simulate the conditions of having a bare transaction setup before we try to do an instant charge. """ user = User.objects.create_user('pm_charge', '*****@*****.**', 'payment_test') # need to create an Account to associate with user from regluit.payment.stripelib import StripeClient, card, Processor sc = StripeClient() # valid card and Account card0 = card() stripe_processor = Processor() account = stripe_processor.make_account(user=user,token=card0) w = Work() w.save() c = Campaign(target=D('1000.00'),deadline=now() + timedelta(days=180),work=w) c.save() t = Transaction(host='stripelib') t.max_amount = D('12.34') t.type = PAYMENT_TYPE_NONE t.status = TRANSACTION_STATUS_NONE t.campaign = c t.approved = False t.user = user t.save() pm = PaymentManager() response = pm.charge(t) self.assertEqual(t.status, TRANSACTION_STATUS_COMPLETE) self.assertEqual(t.type, EXECUTE_TYPE_CHAINED_INSTANT) self.assertEqual(t.amount, D('12.34'))
def setUp(self): edition = models.Edition.objects.get(pk=1) self.work_id = edition.work_id campaign = models.Campaign.objects.create( name=edition.work.title, work=edition.work, description='Test Campaign', deadline=now(), target=Decimal('1000.00'), ) self.user = User.objects.create_user('test', '*****@*****.**', 'testpass') self.client = Client() ebook = models.Ebook.objects.create( url="http://example.com/ebook", provider="github", rights='CC BY', format='epub', edition=edition, )
def recharge_failed_transactions(self): """When a new Account is saved, check whether this is the new active account for a user. If so, recharge any outstanding failed transactions """ transactions_to_recharge = self.user.transaction_set.filter( (Q(status=TRANSACTION_STATUS_FAILED) | Q(status=TRANSACTION_STATUS_ERROR)) & Q(campaign__status='SUCCESSFUL')).all() if transactions_to_recharge: from regluit.payment.manager import PaymentManager pm = PaymentManager() for transaction in transactions_to_recharge: # check whether we are still within the window to recharge if (now() - transaction.deadline_or_now) < datetime.timedelta( settings.RECHARGE_WINDOW): logger.info( "Recharging transaction {0} w/ status {1}".format( transaction.id, transaction.status)) pm.execute_transaction(transaction, [])
def __init__(self, transaction, amount, expiry=None, return_url=None, paymentReason=""): # set the expiration date for the preapproval if not passed in. This is what the paypal library does now_val = now() if expiry is None: expiry = now_val + timedelta(days=settings.PREAPPROVAL_PERIOD) transaction.date_authorized = now_val transaction.date_expired = expiry transaction.save() # Call into our parent class Pay.__init__(self, transaction, return_url=return_url, amount=amount, paymentReason=paymentReason)
def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""): # set the expiration date for the preapproval if not passed in. This is what the paypal library does self.transaction = transaction now_val = now() if expiry is None: expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD ) transaction.date_authorized = now_val transaction.date_expired = expiry # let's figure out what part of transaction can be used to store info # try placing charge id in transaction.pay_key # need to set amount # how does transaction.max_amount get set? -- coming from /pledge/xxx/ -> manager.process_transaction # max_amount is set -- but I don't think we need it for stripe # ASSUMPTION: a user has any given moment one and only one active payment Account account = transaction.user.profile.account if not account: logger.warning("user {0} has no active payment account".format(transaction.user)) raise StripelibError("user {0} has no active payment account".format(transaction.user)) logger.info("user: {0} customer.id is {1}".format(transaction.user, account.account_id)) # settings to apply to transaction for TRANSACTION_STATUS_ACTIVE # should approved be set to False and wait for a webhook? transaction.approved = True transaction.type = PAYMENT_TYPE_AUTHORIZATION transaction.host = PAYMENT_HOST_STRIPE transaction.status = TRANSACTION_STATUS_ACTIVE transaction.preapproval_key = account.account_id transaction.currency = 'USD' transaction.amount = amount transaction.save()
def refresh_acqs(): in_10_min = now() + timedelta(minutes=10) acqs = Acq.objects.filter(refreshed=False, refreshes__lt=in_10_min) logger.info('refreshing %s acqs' % acqs.count()) for acq in acqs: for hold in acq.holds: # create a 1 day reserve on the acq reserve_acq = Acq.objects.create( user=hold.user, work=hold.work, license=RESERVE, lib_acq=acq, ) # the post_save handler takes care of pushing expires vis acq.expires_in # notify the user with the hold if 'example.org' not in reserve_acq.user.email: notification.send([reserve_acq.user], "library_reserve", {'acq': reserve_acq}) # delete the hold hold.delete() break else: acq.refreshed = True
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 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) 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])
def borrowed(self): loans = self.acqs.filter(license=BORROWED, expires__gt=now()) if loans.count() == 0: return None else: return loans[0]
def is_active(self): return self.acqs.filter( expires__isnull=True).count() > 0 or self.acqs.filter( expires__gt=now()).count() > 0
def add_openlibrary(work, hard_refresh=False): if (not hard_refresh) and work.openlibrary_lookup is not None: # don't hit OL if we've visited in the past month or so if now() - work.openlibrary_lookup < timedelta(days=30): return work.openlibrary_lookup = now() work.save() # find the first ISBN match in OpenLibrary logger.info("looking up openlibrary data for work %s", work.id) e = None # openlibrary edition json w = None # openlibrary work json # get the 1st openlibrary match by isbn that has an associated work url = "https://openlibrary.org/api/books" params = {"format": "json", "jscmd": "details"} subjects = [] for edition in work.editions.all(): isbn_key = "ISBN:%s" % edition.isbn_13 params['bibkeys'] = isbn_key try: e = _get_json(url, params, type='ol') except LookupFailure: logger.exception("OL lookup failed for %s", isbn_key) e = {} if e.has_key(isbn_key): if e[isbn_key].has_key('details'): if e[isbn_key]['details'].has_key('oclc_numbers'): for oclcnum in e[isbn_key]['details']['oclc_numbers']: models.Identifier.get_or_add(type='oclc', value=oclcnum, work=work, edition=edition) if e[isbn_key]['details'].has_key('identifiers'): ids = e[isbn_key]['details']['identifiers'] if ids.has_key('goodreads'): models.Identifier.get_or_add(type='gdrd', value=ids['goodreads'][0], work=work, edition=edition) if ids.has_key('librarything'): models.Identifier.get_or_add( type='ltwk', value=ids['librarything'][0], work=work) if ids.has_key('google'): models.Identifier.get_or_add(type='goog', value=ids['google'][0], work=work) if ids.has_key('project_gutenberg'): models.Identifier.get_or_add( type='gute', value=ids['project_gutenberg'][0], work=work) if e[isbn_key]['details'].has_key('works'): work_key = e[isbn_key]['details']['works'].pop(0)['key'] logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key) models.Identifier.get_or_add(type='olwk', value=work_key, work=work) try: w = _get_json("https://openlibrary.org" + work_key, type='ol') if w.has_key('description'): description = w['description'] if isinstance(description, dict): if description.has_key('value'): description = description['value'] description = despam_description(description) if not work.description or work.description.startswith( '{') or len(description) > len( work.description): work.description = description work.save() if w.has_key('subjects') and len( w['subjects']) > len(subjects): subjects = w['subjects'] except LookupFailure: logger.exception("OL lookup failed for %s", work_key) if not subjects: logger.warn("unable to find work %s at openlibrary", work.id) return # add the subjects to the Work for s in subjects: logger.info("adding subject %s to work %s", s, work.id) subject = models.Subject.set_by_name(s, work=work) work.save()
def __init__(self, transaction=None): self.transaction = transaction # make sure transaction hasn't already been executed if transaction.status == TRANSACTION_STATUS_COMPLETE: return # make sure we are dealing with a stripe transaction if transaction.host <> PAYMENT_HOST_STRIPE: raise StripelibError("transaction.host {0} is not the expected {1}".format(transaction.host, PAYMENT_HOST_STRIPE)) sc = StripeClient() # look first for transaction.user.profile.account.account_id try: customer_id = transaction.user.profile.account.account_id except: customer_id = transaction.preapproval_key if customer_id is not None: try: # useful things to put in description: transaction.id, transaction.user.id, customer_id, transaction.amount charge = sc.create_charge(transaction.amount, customer=customer_id, description=json.dumps({"t.id":transaction.id, "email":transaction.user.email if transaction.user else transaction.receipt, "cus.id":customer_id, "tc.id": transaction.campaign.id if transaction.campaign else '0', "amount": float(transaction.amount)})) except stripe.StripeError as e: # what to record in terms of errors? (error log?) # use PaymentResponse to store error r = PaymentResponse.objects.create(api="stripelib.Execute", correlation_id=None, timestamp=now(), info=e.message, status=TRANSACTION_STATUS_ERROR, transaction=transaction) transaction.status = TRANSACTION_STATUS_ERROR self.errorMessage = e.message # manager puts this on transaction transaction.save() # fire off the fact that transaction failed -- should actually do so only if not a transient error # if card_declined or expired card, ask user to update account if isinstance(e, stripe.CardError) and e.code in ('card_declined', 'expired_card', 'incorrect_number', 'processing_error'): transaction_failed.send(sender=self, transaction=transaction) # otherwise, report exception to us else: logger.exception("transaction id {0}, exception: {1}".format(transaction.id, e.message)) # raise StripelibError(e.message, e) else: self.charge = charge transaction.status = TRANSACTION_STATUS_COMPLETE transaction.pay_key = charge.id transaction.date_payment = now() transaction.save() # fire signal for sucessful transaction transaction_charged.send(sender=self, transaction=transaction) else: # nothing to charge raise StripelibError("No customer id available to charge for transaction {0}".format(transaction.id), None)
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 set_payment(self): self.date_payment = now() self.save()
def deactivate(self): """Don't allow more than one active Account of given host to be associated with a given user""" self.date_deactivated = now() self.status = 'DEACTIVATED' self.save()
def set_executed(self): self.date_executed = now() self.save()
def borrowable(self): return self.acqs.filter(license=LIBRARY, refreshes__lt=now()).count() > 0
def borrowable_acq(self): for acq in self.acqs.filter(license=LIBRARY, refreshes__lt=now()): return acq
def deadline_or_now(self): if self.campaign and self.campaign.deadline: return self.campaign.deadline else: return now()