def index_finance_total_by_src(addons, **kw): """ Bug 758059 Total finance stats, source breakdown. """ es = elasticutils.get_es() log.info('Indexing total financial stats by source for %s apps.' % len(addons)) for addon in addons: # Get all contributions for given add-on. qs = Contribution.objects.filter(addon=addon, uuid=None) if not qs.exists(): continue # Get list of distinct sources. sources = set(qs.values_list('source', flat=True)) for source in sources: try: key = ord_word('src' + str(addon) + str(source)) data = search.get_finance_total_by_src(qs, addon, source) if not already_indexed(Contribution, data): Contribution.index(data, bulk=True, id=key) es.flush_bulk(forced=True) except Exception, exc: index_finance_total_by_src.retry(args=[addons], exc=exc) raise
def index_finance_total_by_currency(addons, **kw): """ Bug 757581 Total finance stats, currency breakdown. """ es = elasticutils.get_es() log.info('Indexing total financial stats by currency for %s apps.' % len(addons)) for addon in addons: # Get all contributions for given add-on. qs = Contribution.objects.filter(addon=addon, uuid=None) if not qs.exists(): continue # Get list of distinct currencies. currencies = set(qs.values_list('currency', flat=True)) for currency in currencies: try: key = ord_word('cur' + str(addon) + currency.lower()) data = search.get_finance_total_by_currency( qs, addon, currency) if not already_indexed(Contribution, data): Contribution.index(data, bulk=True, id=key) es.flush_bulk(forced=True) except Exception, exc: index_finance_total_by_currency.retry(args=[addons], exc=exc) raise
def index_finance_total_by_src(addons, **kw): """ Bug 758059 Total finance stats, source breakdown. """ index = kw.get('index', Contribution._get_index()) es = amo.search.get_es() log.info('Indexing total financial stats by source for %s apps.' % len(addons)) for addon in addons: # Get all contributions for given add-on. qs = Contribution.objects.filter(addon=addon, uuid=None) if not qs.exists(): continue # Get list of distinct sources. sources = set(qs.values_list('source', flat=True)) for source in sources: try: key = ord_word('src' + str(addon) + str(source)) data = search.get_finance_total(qs, addon, 'source', source=source) for index in get_indices(index): if not already_indexed(Contribution, data, index): Contribution.index(data, bulk=True, id=key, index=index) es.flush_bulk(forced=True) except Exception, exc: index_finance_total_by_src.retry(args=[addons], exc=exc, **kw) raise
def index_contribution_counts(ids, **kw): """ Contribution stats by addon-date unique pair Uses a nested dictionary to not index duplicate contribution with same addon/date pairs. For each addon-date, it stores the addon in the dict as a top level key with a dict as its value. And it stores the date in the addon's dict as a second level key. To check if an addon-date pair has been already index, it looks up the dict[addon][date] to see if the key exists. """ es = elasticutils.get_es() qs = (Contribution.objects.filter(id__in=ids) .order_by('created').values('addon', 'created')) try: addons_dates = defaultdict(lambda: defaultdict(dict)) for contribution in qs: addon = contribution['addon'] date = contribution['created'].strftime('%Y%m%d') # date for addon not processed, index it and give it key if not date in addons_dates[addon]: key = '%s-%s' % (addon, date) data = search.extract_contribution_counts(contribution) Contribution.index(data, bulk=True, id=key) addons_dates[addon][date] = 0 if qs: log.info('Indexed %s addons/apps for contribution stats: %s' % (len(addons_dates), qs[0]['created'])) es.flush_bulk(forced=True) except Exception, exc: index_contribution_counts.retry(args=[ids], exc=exc) raise
def index_finance_total_by_currency(addons, **kw): """ Bug 757581 Total finance stats, currency breakdown. """ index = kw.get('index', Contribution._get_index()) es = amo.search.get_es() log.info('Indexing total financial stats by currency for %s apps.' % len(addons)) for addon in addons: # Get all contributions for given add-on. qs = Contribution.objects.filter(addon=addon, uuid=None) if not qs.exists(): continue # Get list of distinct currencies. currencies = set(qs.values_list('currency', flat=True)) for currency in currencies: try: key = ord_word('cur' + str(addon) + currency.lower()) data = search.get_finance_total( qs, addon, 'currency', currency=currency) for index in get_indices(index): if not already_indexed(Contribution, data, index): Contribution.index(data, bulk=True, id=key, index=index) es.flush_bulk(forced=True) except Exception, exc: index_finance_total_by_currency.retry(args=[addons], exc=exc, **kw) raise
def index_contribution_counts(ids, **kw): """ Contribution stats by addon-date unique pair Uses a nested dictionary to not index duplicate contribution with same addon/date pairs. For each addon-date, it stores the addon in the dict as a top level key with a dict as its value. And it stores the date in the addon's dict as a second level key. To check if an addon-date pair has been already index, it looks up the dict[addon][date] to see if the key exists. """ es = elasticutils.get_es() qs = (Contribution.objects.filter(id__in=ids).order_by('created').values( 'addon', 'created')) try: addons_dates = defaultdict(lambda: defaultdict(dict)) for contribution in qs: addon = contribution['addon'] date = contribution['created'].strftime('%Y%m%d') # date for addon not processed, index it and give it key if not date in addons_dates[addon]: key = '%s-%s' % (addon, date) data = search.extract_contribution_counts(contribution) Contribution.index(data, bulk=True, id=key) addons_dates[addon][date] = 0 if qs: log.info('Indexed %s addons/apps for contribution stats: %s' % (len(addons_dates), qs[0]['created'])) es.flush_bulk(forced=True) except Exception, exc: index_contribution_counts.retry(args=[ids], exc=exc) raise
def contribute(request, addon): contrib_type = request.GET.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.GET.get('source', '') comment = request.GET.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.GET.get('onetime-amount', '')}.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: name, paypal_id = ('%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) paykey, error = '', '' try: paykey = paypal.get_paykey(dict(uuid=contribution_uuid, slug=addon.slug, amount=amount, email=paypal_id, memo=contrib_for, ip=request.META.get('REMOTE_ADDR'), pattern='addons.paypal')) except: log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) error = _('There was an error communicating with PayPal.') if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() assert settings.PAYPAL_FLOW_URL, 'settings.PAYPAL_FLOW_URL is not defined' url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': error}), content_type='application/json') return http.HttpResponseRedirect(url)
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.GET.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u'Purchase of {0}').format(jinja2.escape(addon.name)) paykey, error = '', '' try: pattern = 'addons.purchase.finished' slug = addon.slug if addon.is_webapp(): pattern = 'apps.purchase.finished' slug = addon.app_slug paykey = paypal.get_paykey( dict(uuid=uuid_, slug=slug, amount=amount, memo=contrib_for, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), pattern=pattern, qs={'realurl': request.GET.get('realurl')}, chains=settings.PAYPAL_CHAINS)) except: log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) error = _('There was an error communicating with PayPal.') if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': error }), content_type='application/json') return http.HttpResponseRedirect(url)
def index_latest_mkt_stats(index=None, aliased=True): raise_if_reindex_in_progress() yesterday = datetime.date.today() - datetime.timedelta(days=1) try: latest = Contribution.search(index).order_by('-date').values_dict() latest_contribution = latest and latest[0]['date'] or yesterday except pyes.exceptions.SearchPhaseExecutionException: latest_contribution = yesterday try: latest = Installed.search(index).order_by('-date').values_dict() latest_install = latest and latest[0]['date'] or yesterday except pyes.exceptions.SearchPhaseExecutionException: latest_install = yesterday latest = min(latest_contribution, latest_install) fmt = lambda d: d.strftime('%Y-%m-%d') date_range = '%s:%s' % (fmt(latest), fmt(datetime.date.today())) cron_log.info('index_mkt_stats --date=%s' % date_range) call_command('index_mkt_stats', addons=None, date=date_range, index=index, aliased=True)
def test_index(self): tasks.index_finance_total_by_currency([self.app.pk]) self.refresh(timesleep=1) raise SkipTest('Test is unreliable and causes intermittent failures.') # Grab document for each source breakdown and compare. for currency in self.currencies: # For some reason, query fails if uppercase letter in filter. document = (Contribution.search().filter(addon=self.app.pk, currency=currency.lower()).values_dict('currency', 'revenue', 'count', 'refunds', 'revenue_non_normalized')[0]) document = { 'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds'], 'revenue_non_normalized': int(document['revenue_non_normalized'])} self.expected[currency]['revenue'] = ( int(self.expected[currency]['revenue']) ) self.expected[currency]['revenue_non_normalized'] = ( int(self.expected[currency]['revenue_non_normalized']) ) eq_(document, self.expected[currency])
def purchase(request, addon): log.debug("Starting purchase of addon: %s by user: %s" % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.GET.get("source", "") uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u"Purchase of {0}").format(jinja2.escape(addon.name)) paykey, error = "", "" try: paykey = paypal.get_paykey( dict( uuid=uuid_, slug=addon.slug, amount=amount, memo=contrib_for, email=addon.paypal_id, ip=request.META.get("REMOTE_ADDR"), pattern="addons.purchase.finished", qs={"realurl": request.GET.get("realurl")}, ipn=False, ) ) except: log.error("Error getting paykey, purchase of addon: %s" % addon.pk, exc_info=True) error = _("There was an error communicating with PayPal.") if paykey: contrib = Contribution( addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, ) contrib.save() log.debug("Got paykey for addon: %s by user: %s" % (addon.pk, request.amo_user.pk)) url = "%s?paykey=%s" % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get("result_type") == "json" or request.is_ajax(): return http.HttpResponse( json.dumps({"url": url, "paykey": paykey, "error": error}), content_type="application/json" ) return http.HttpResponseRedirect(url)
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.GET.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u'Purchase of {0}').format(jinja2.escape(addon.name)) paykey, error = '', '' try: pattern = 'addons.purchase.finished' slug = addon.slug if addon.is_webapp(): pattern = 'apps.purchase.finished' slug = addon.app_slug paykey = paypal.get_paykey(dict(uuid=uuid_, slug=slug, amount=amount, memo=contrib_for, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), pattern=pattern, qs={'realurl': request.GET.get('realurl')}, chains=settings.PAYPAL_CHAINS)) except: log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) error = _('There was an error communicating with PayPal.') if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': error}), content_type='application/json') return http.HttpResponseRedirect(url)
def index_finance_daily(ids, **kw): """ Bug 748015 Takes a list of Contribution ids and uses its addon and date fields to index stats for that day. Contribution stats by addon-date unique pair. Uses a nested dictionary to not index duplicate contribution with same addon/date pairs. For each addon-date, it stores the addon in the dict as a top level key with a dict as its value. And it stores the date in the add-on's dict as a second level key. To check if an addon-date pair has been already index, it looks up the dict[addon][date] to see if the key exists. This adds some speed up when batch processing. ids -- ids of apps.stats.Contribution objects """ index = kw.get('index', Contribution._get_index()) es = amo.search.get_es() # Get contributions. qs = (Contribution.objects.filter(id__in=ids).order_by('created').values( 'addon', 'created')) log.info('[%s] Indexing %s contributions for daily stats.' % (qs[0]['created'], len(ids))) addons_dates = defaultdict(lambda: defaultdict(dict)) for contribution in qs: addon = contribution['addon'] date = contribution['created'].strftime('%Y%m%d') try: # Date for add-on not processed, index it and give it key. if not date in addons_dates[addon]: key = ord_word('fin' + str(addon) + str(date)) data = search.get_finance_daily(contribution) for index in get_indices(index): if not already_indexed(Contribution, data, index): Contribution.index(data, bulk=True, id=key, index=index) addons_dates[addon][date] = 0 es.flush_bulk(forced=True) except Exception, exc: index_finance_daily.retry(args=[ids], exc=exc, **kw) raise
def old_contribute(request, addon): contrib_type = request.GET.get('type', '') is_suggested = contrib_type == 'suggested' source = request.GET.get('source', '') comment = request.GET.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.GET.get('onetime-amount', ''), 'monthly': request.GET.get('monthly-amount', '')}.get(contrib_type, '') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment) contrib.save() return_url = "%s?%s" % (reverse('addons.thanks', args=[addon.slug]), urllib.urlencode({'uuid': contribution_uuid})) # L10n: {0} is an add-on name. if addon.charity: name, paypal = addon.charity.name, addon.charity.paypal else: name, paypal = addon.name, addon.paypal_id contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) redirect_url_params = contribute_url_params( paypal, addon.id, contrib_for, absolutify(return_url), amount, contribution_uuid, contrib_type == 'monthly', comment) return http.HttpResponseRedirect(settings.PAYPAL_CGI_URL + '?' + urllib.urlencode(redirect_url_params))
def contribute(request, addon_id): addon = get_object_or_404(Addon.objects.valid(), id=addon_id) contrib_type = request.GET.get('type', '') is_suggested = contrib_type == 'suggested' source = request.GET.get('source', '') comment = request.GET.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.GET.get('onetime-amount', ''), 'monthly': request.GET.get('monthly-amount', '')}.get(contrib_type, '') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment) contrib.save() return_url = "%s?%s" % (reverse('addons.thanks', args=[addon.id]), urllib.urlencode({'uuid': contribution_uuid})) """ L10n: Phrase that will appear on the paypal site specifying who the contribution is for""" contrib_for = _('Contribution for {addon_name}').format(addon_name=addon.name) redirect_url_params = contribute_url_params( addon.paypal_id, addon.id, contrib_for, absolutify(return_url), amount, contribution_uuid, contrib_type == 'monthly', comment) return http.HttpResponseRedirect(settings.PAYPAL_CGI_URL + '?' + urllib.urlencode(redirect_url_params))
def index_finance_daily(ids, **kw): """ Bug 748015 Takes a list of Contribution ids and uses its addon and date fields to index stats for that day. Contribution stats by addon-date unique pair. Uses a nested dictionary to not index duplicate contribution with same addon/date pairs. For each addon-date, it stores the addon in the dict as a top level key with a dict as its value. And it stores the date in the add-on's dict as a second level key. To check if an addon-date pair has been already index, it looks up the dict[addon][date] to see if the key exists. This adds some speed up when batch processing. ids -- ids of apps.stats.Contribution objects """ index = kw.get('index', Contribution._get_index()) es = amo.search.get_es() # Get contributions. qs = (Contribution.objects.filter(id__in=ids) .order_by('created').values('addon', 'created')) log.info('[%s] Indexing %s contributions for daily stats.' % (qs[0]['created'], len(ids))) addons_dates = defaultdict(lambda: defaultdict(dict)) for contribution in qs: addon = contribution['addon'] date = contribution['created'].strftime('%Y%m%d') try: # Date for add-on not processed, index it and give it key. if not date in addons_dates[addon]: key = ord_word('fin' + str(addon) + str(date)) data = search.get_finance_daily(contribution) for index in get_indices(index): if not already_indexed(Contribution, data, index): Contribution.index(data, bulk=True, id=key, index=index) addons_dates[addon][date] = 0 es.flush_bulk(forced=True) except Exception, exc: index_finance_daily.retry(args=[ids], exc=exc, **kw) raise
def test_index(self): tasks.index_finance_total([self.app.pk]) self.refresh(timesleep=1) document = Contribution.search().filter(addon=self.app.pk).values_dict("revenue", "count", "refunds")[0] document = {"count": document["count"], "revenue": int(document["revenue"]), "refunds": document["refunds"]} self.expected["revenue"] = int(self.expected["revenue"]) eq_(document, self.expected)
def test_total_contributions(self): c = Contribution() c.addon_id = 3615 c.amount = "9.99" c.save() tasks.addon_total_contributions(3615) a = Addon.objects.no_cache().get(pk=3615) eq_(float(a.total_contributions), 9.99) c = Contribution() c.addon_id = 3615 c.amount = "10.00" c.save() tasks.addon_total_contributions(3615) a = Addon.objects.no_cache().get(pk=3615) eq_(float(a.total_contributions), 19.99)
def old_contribute(request, addon): contrib_type = request.GET.get('type', '') is_suggested = contrib_type == 'suggested' source = request.GET.get('source', '') comment = request.GET.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.GET.get('onetime-amount', ''), 'monthly': request.GET.get('monthly-amount', '') }.get(contrib_type, '') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment) contrib.save() return_url = "%s?%s" % (reverse('addons.thanks', args=[addon.slug]), urllib.urlencode({'uuid': contribution_uuid})) # L10n: {0} is an add-on name. if addon.charity: name, paypal = addon.charity.name, addon.charity.paypal else: name, paypal = addon.name, addon.paypal_id contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) redirect_url_params = contribute_url_params(paypal, addon.id, contrib_for, absolutify(return_url), amount, contribution_uuid, contrib_type == 'monthly', comment) return http.HttpResponseRedirect(settings.PAYPAL_CGI_URL + '?' + urllib.urlencode(redirect_url_params))
def index_addon_aggregate_contributions(addons, **kw): """ Aggregates stats from all of the contributions for a given addon """ es = elasticutils.get_es() log.info('Aggregating total contribution stats for %s addons' % len(addons)) try: for addon in addons: # Only count uuid=None; those are verified transactions. qs = Contribution.objects.filter(addon__in=addons, uuid=None) # Create lists of annotated dicts [{'addon':1, 'revenue':5}...] revenues = qs.values('addon').annotate(revenue=Sum('amount')) sales = qs.values('addon').annotate(sales=Count('id')) refunds = (qs.filter( refund__isnull=False).values('addon').annotate( refunds=Count('id'))) # Loop over revenue, sales, refunds. data_dict = defaultdict(lambda: defaultdict(dict)) for revenue in revenues: data_dict[str( revenue['addon'])]['revenue'] = revenue['revenue'] for sale in sales: data_dict[str(sale['addon'])]['sales'] = sale['sales'] for refund in refunds: data_dict[str(refund['addon'])]['refunds'] = refund['refunds'] for addon, addon_dict in data_dict.iteritems(): data = { 'addon': addon, 'count': addon_dict['sales'], 'revenue': addon_dict['revenue'], 'refunds': addon_dict['refunds'], } Contribution.index(data, bulk=True, id=addon) es.flush_bulk(forced=True) except Exception, exc: index_addon_aggregate_contributions.retry(args=[addons], exc=exc) raise
def index_finance_total(addons, **kw): """ Aggregates financial stats from all of the contributions for a given app. """ es = elasticutils.get_es() log.info('Indexing total financial stats for %s apps.' % len(addons)) for addon in addons: # Get all contributions for given add-on. qs = Contribution.objects.filter(addon=addon, uuid=None) if not qs.exists(): continue try: key = ord_word('tot' + str(addon)) data = search.get_finance_total(qs, addon) if not already_indexed(Contribution, data): Contribution.index(data, bulk=True, id=key) es.flush_bulk(forced=True) except Exception, exc: index_finance_total.retry(args=[addons], exc=exc) raise
def index_latest_mkt_stats(): latest_contribution = Contribution.search().order_by( '-date').values_dict()[0]['date'] latest_install = Installed.search().order_by( '-date').values_dict()[0]['date'] latest = min(latest_contribution, latest_install) fmt = lambda d: d.strftime('%Y-%m-%d') date_range = '%s:%s' % (fmt(latest), fmt(datetime.date.today())) cron_log.info('index_mkt_stats --date=%s' % date_range) call_command('index_mkt_stats', addons=None, date=date_range)
def index_latest_mkt_stats(): latest_contribution = Contribution.search().order_by('-date' ).values_dict()[0]['date'] latest_install = Installed.search().order_by('-date' ).values_dict()[0]['date'] latest = min(latest_contribution, latest_install) fmt = lambda d: d.strftime('%Y-%m-%d') date_range = '%s:%s' % (fmt(latest), fmt(datetime.date.today())) cron_log.info('index_mkt_stats --date=%s' % date_range) call_command('index_mkt_stats', addons=None, date=date_range)
def index_addon_aggregate_contributions(addons, **kw): """ Aggregates stats from all of the contributions for a given addon """ es = elasticutils.get_es() log.info('Aggregating total contribution stats for %s addons' % len(addons)) try: for addon in addons: # Only count uuid=None; those are verified transactions. qs = Contribution.objects.filter(addon__in=addons, uuid=None) # Create lists of annotated dicts [{'addon':1, 'revenue':5}...] revenues = qs.values('addon').annotate(revenue=Sum('amount')) sales = qs.values('addon').annotate(sales=Count('id')) refunds = (qs.filter(refund__isnull=False). values('addon').annotate(refunds=Count('id'))) # Loop over revenue, sales, refunds. data_dict = defaultdict(lambda: defaultdict(dict)) for revenue in revenues: data_dict[str( revenue['addon'])]['revenue'] = revenue['revenue'] for sale in sales: data_dict[str(sale['addon'])]['sales'] = sale['sales'] for refund in refunds: data_dict[str(refund['addon'])]['refunds'] = refund['refunds'] for addon, addon_dict in data_dict.iteritems(): data = { 'addon': addon, 'count': addon_dict['sales'], 'revenue': addon_dict['revenue'], 'refunds': addon_dict['refunds'], } Contribution.index(data, bulk=True, id=addon) es.flush_bulk(forced=True) except Exception, exc: index_addon_aggregate_contributions.retry(args=[addons], exc=exc) raise
def test_index(self): tasks.index_finance_total([self.app.pk]) self.refresh(timesleep=1) document = Contribution.search().filter(addon=self.app.pk ).values_dict('revenue', 'count', 'refunds')[0] document = {'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds']} self.expected['revenue'] = int(self.expected['revenue']) eq_(document, self.expected)
def test_index(self): tasks.index_finance_total_by_src([self.app.pk]) self.refresh(timesleep=1) # Grab document for each source breakdown and compare. for source in self.sources: # For some reason, query fails if uppercase letter in filter. document = ( Contribution.search() .filter(addon=self.app.pk, source=source.lower()) .values_dict("source", "revenue", "count", "refunds")[0] ) document = {"count": document["count"], "revenue": int(document["revenue"]), "refunds": document["refunds"]} self.expected[source]["revenue"] = int(self.expected[source]["revenue"]) eq_(document, self.expected[source])
def test_index(self): tasks.index_finance_total([self.app.pk]) self.refresh(timesleep=1) document = Contribution.search().filter(addon=self.app.pk).values_dict( 'revenue', 'count', 'refunds')[0] document = { 'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds'] } self.expected['revenue'] = int(self.expected['revenue']) eq_(document, self.expected)
def test_index(self): tasks.index_finance_daily.delay(self.ids) self.refresh(timesleep=1) document = Contribution.search().filter(addon=self.app.pk).values_dict("date", "revenue", "count", "refunds")[0] date = document["date"] ex_date = self.expected["date"] eq_((date.year, date.month, date.day), (ex_date.year, ex_date.month, ex_date.day)) document = {"count": document["count"], "revenue": int(document["revenue"]), "refunds": document["refunds"]} del (self.expected["date"]) self.expected["revenue"] = int(self.expected["revenue"]) eq_(document, self.expected)
def test_index(self): tasks.index_finance_total_by_src([self.app.pk]) self.refresh(timesleep=1) # Grab document for each source breakdown and compare. for source in self.sources: # For some reason, query fails if uppercase letter in filter. document = (Contribution.search().filter(addon=self.app.pk, source=source.lower()).values_dict('source', 'revenue', 'count', 'refunds')[0]) document = {'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds']} self.expected[source]['revenue'] = ( int(self.expected[source]['revenue']) ) eq_(document, self.expected[source])
def test_index(self): tasks.index_finance_total_by_src([self.app.pk]) self.refresh(timesleep=1) # Grab document for each source breakdown and compare. for source in self.sources: # For some reason, query fails if uppercase letter in filter. document = (Contribution.search().filter( addon=self.app.pk, source=source.lower()).values_dict('source', 'revenue', 'count', 'refunds')[0]) document = { 'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds'] } self.expected[source]['revenue'] = (int( self.expected[source]['revenue'])) eq_(document, self.expected[source])
def test_index(self): tasks.index_finance_daily.delay(self.ids) self.refresh(timesleep=1) document = Contribution.search().filter(addon=self.app.pk ).values_dict('date', 'revenue', 'count', 'refunds')[0] date = document['date'] ex_date = self.expected['date'] eq_((date.year, date.month, date.day), (ex_date.year, ex_date.month, ex_date.day)) document = {'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds']} del(self.expected['date']) self.expected['revenue'] = int(self.expected['revenue']) eq_(document, self.expected)
def test_index(self): tasks.index_finance_total_by_currency([self.app.pk]) self.refresh(timesleep=1) # Grab document for each source breakdown and compare. for currency in self.currencies: # For some reason, query fails if uppercase letter in filter. document = ( Contribution.search() .filter(addon=self.app.pk, currency=currency.lower()) .values_dict("currency", "revenue", "count", "refunds", "revenue_non_normalized")[0] ) document = { "count": document["count"], "revenue": int(document["revenue"]), "refunds": document["refunds"], "revenue_non_normalized": int(document["revenue_non_normalized"]), } self.expected[currency]["revenue"] = int(self.expected[currency]["revenue"]) self.expected[currency]["revenue_non_normalized"] = int(self.expected[currency]["revenue_non_normalized"]) eq_(document, self.expected[currency])
def test_index(self): tasks.index_finance_daily.delay(self.ids) self.refresh(timesleep=1) document = Contribution.search().filter(addon=self.app.pk).values_dict( 'date', 'revenue', 'count', 'refunds')[0] date = document['date'] ex_date = self.expected['date'] eq_((date.year, date.month, date.day), (ex_date.year, ex_date.month, ex_date.day)) document = { 'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds'] } del (self.expected['date']) self.expected['revenue'] = int(self.expected['revenue']) eq_(document, self.expected)
def test_index(self): tasks.index_finance_total_by_currency([self.app.pk]) self.refresh(timesleep=1) # Grab document for each source breakdown and compare. for currency in self.currencies: # For some reason, query fails if uppercase letter in filter. document = (Contribution.search().filter( addon=self.app.pk, currency=currency.lower()).values_dict( 'currency', 'revenue', 'count', 'refunds', 'revenue_non_normalized')[0]) document = { 'count': document['count'], 'revenue': int(document['revenue']), 'refunds': document['refunds'], 'revenue_non_normalized': int(document['revenue_non_normalized']) } self.expected[currency]['revenue'] = (int( self.expected[currency]['revenue'])) self.expected[currency]['revenue_non_normalized'] = (int( self.expected[currency]['revenue_non_normalized'])) eq_(document, self.expected[currency])
def purchase(request, addon): log.debug("Starting purchase of addon: %s by user: %s" % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get("source", "") uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u"Purchase of {0}").format(jinja2.escape(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), "USD" # If tier is specified, then let's look it up. form = PriceCurrencyForm(data=request.POST, price=addon.premium.price) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency paykey, status, error = "", "", "" preapproval = None if waffle.flag_is_active(request, "allow-pre-auth") and request.amo_user: preapproval = request.amo_user.get_preapproval() try: pattern = "addons.purchase.finished" slug = addon.slug if addon.is_webapp(): pattern = "apps.purchase.finished" slug = addon.app_slug paykey, status = paypal.get_paykey( dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get("REMOTE_ADDR"), memo=contrib_for, pattern=pattern, preapproval=preapproval, qs={"realurl": request.POST.get("realurl")}, slug=slug, uuid=uuid_, ) ) except paypal.PaypalError as error: paypal.paypal_log_cef( request, addon, uuid_, "PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error("Error getting paykey, purchase of addon: %s" % addon.pk, exc_info=True) if paykey: contrib = Contribution( addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, ) log.debug("Storing contrib for uuid: %s" % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == "COMPLETED": paypal.paypal_log_cef(request, addon, uuid_, "Purchase", "PURCHASE", "A user purchased using pre-approval") log.debug("Status is completed for uuid: %s" % uuid_) if paypal.check_purchase(paykey) == "COMPLETED": log.debug("Check purchase is completed for uuid: %s" % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error("Check purchase failed on uuid: %s" % uuid_) status = "NOT-COMPLETED" contrib.save() else: log.error("No paykey present for uuid: %s" % uuid_) log.debug("Got paykey for addon: %s by user: %s" % (addon.pk, request.amo_user.pk)) url = "%s?paykey=%s" % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get("result_type") == "json" or request.is_ajax(): return http.HttpResponse( json.dumps({"url": url, "paykey": paykey, "error": str(error), "status": status}), content_type="application/json", ) # This is the non-Ajax fallback. if status != "COMPLETED": return redirect(url) messages.success(request, _("Purchase complete")) return redirect(shared_url("addons.detail", addon))
except paypal.PaypalError, exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc) with transaction.commit_on_success(): contrib = Contribution(addon_id=product.id, amount=price, source=source, source_locale=request.LANG, currency=currency, uuid=str(uuid_), price_tier=tier, type=amo.CONTRIB_INAPP_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing in-app payment contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, product, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'):
def purchase(request, addon): amount, currency, uuid_, contrib_for = start_purchase(request, addon) if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED'}), content_type='application/json') paykey, status, error = '', '', '' # TODO(solitude): remove this, pre-approval and currency will be # stored in solitude. preapproval = None if (not waffle.flag_is_active(request, 'solitude-payments') and request.amo_user): preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency if waffle.flag_is_active(request, 'solitude-payments'): # Now call the client. result = {} try: result = client.pay({'amount': amount, 'currency': currency, 'buyer': request.amo_user, 'seller': addon, 'memo': contrib_for}) except client.Error as error: # Note that by assigning this to error, it will go into the return # value for the json. General solitude errors will then be # reported back to the user. paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey: %s' % addon.pk, exc_info=True) # TODO(solitude): just use the dictionary when solitude is live. paykey = result.get('pay_key', '') status = result.get('status', '') uuid_ = result.get('uuid', '') else: # TODO(solitude): remove this when solitude goes live. try: paykey, status = paypal.get_paykey(dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_ )) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: # TODO(solitude): at some point we'll have to see what to do with # contributions. download_source = request.REQUEST.get('src', '') contrib = Contribution(addon_id=addon.id, amount=amount, source=download_source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, price_tier=addon.premium.price, client_data=ClientData.get_or_create(request)) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED': contrib.type = amo.CONTRIB_PURCHASE else: log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' else: #TODO(solitude): remove this when solitude goes live. if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(addon.get_detail_url())
def contribute(request, addon): webapp = addon.is_webapp() contrib_type = request.GET.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.GET.get('source', '') comment = request.GET.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.GET.get('onetime-amount', '') }.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u'%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() paykey, error, status = '', '', '' try: paykey, status = paypal.get_paykey( dict(amount=amount, email=paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='%s.paypal' % ('apps' if webapp else 'addons'), preapproval=preapproval, slug=addon.slug, uuid=contribution_uuid)) except: paypal.paypal_log_cef(request, addon, contribution_uuid, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) error = _('There was an error communicating with PayPal.') if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': error, 'status': status }), content_type='application/json') return http.HttpResponseRedirect(url)
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # L10n: {0} is the addon name. contrib_for = (_(u'Mozilla Marketplace purchase of {0}') .format(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. if waffle.switch_is_active('currencies'): form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED'}), content_type='application/json') paykey, status, error = '', '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency try: paykey, status = paypal.get_paykey(dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_ )) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return redirect(url) messages.success(request, _('Purchase complete')) return redirect(addon.get_detail_url())
def contribute(request, addon): webapp = addon.is_webapp() contrib_type = request.POST.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.POST.get('source', '') comment = request.POST.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.POST.get('onetime-amount', '') }.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION # This is all going to get shoved into solitude. Temporary. form = ContributionForm({'amount': amount}) if not form.is_valid(): return http.HttpResponse(json.dumps({'error': 'Invalid data.', 'status': '', 'url': '', 'paykey': ''}), content_type='application/json') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u'%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() paykey, error, status = '', '', '' try: paykey, status = paypal.get_paykey( dict(amount=amount, email=paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='%s.paypal' % ('apps' if webapp else 'addons'), preapproval=preapproval, slug=addon.slug, uuid=contribution_uuid)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, contribution_uuid, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') return http.HttpResponseRedirect(url)
def contribute(request, addon): commentlimit = 255 # Enforce paypal-imposed comment length limit contrib_type = request.POST.get('type', 'suggested') is_suggested = contrib_type == 'suggested' source = request.POST.get('source', '') comment = request.POST.get('comment', '') amount = { 'suggested': addon.suggested_amount, 'onetime': request.POST.get('onetime-amount', '') }.get(contrib_type, '') if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION form = ContributionForm({'amount': amount}) if len(comment) > commentlimit or not form.is_valid(): return http.HttpResponse(json.dumps({'error': 'Invalid data.', 'status': '', 'url': '', 'paykey': ''}), content_type='application/json') contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u'%s: %s' % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u'Contribution for {0}').format(jinja2.escape(name)) paykey, error, status = '', '', '' try: paykey, status = paypal.get_paykey( dict(amount=amount, email=paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='addons.paypal', slug=addon.slug, uuid=contribution_uuid)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, contribution_uuid, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, contribution for addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') return http.HttpResponseRedirect(url)
'amount': str(amount), 'email': paypal_id, 'ip': request.META.get('REMOTE_ADDR'), 'memo': contrib_for}) except paypal.AuthError, error: paypal_log.error('Authentication error: %s' % error) except Exception, error: paypal_log.error('Error: %s' % error) if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey}), content_type='application/json') elif paykey is None: # If there was an error getting the paykey, raise this.
def purchase(request, addon): amount, currency, uuid_, contrib_for = start_purchase(request, addon) if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({ 'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED' }), content_type='application/json') paykey, status, error = '', '', '' # TODO(solitude): remove this, pre-approval and currency will be # stored in solitude. preapproval = None if (not waffle.flag_is_active(request, 'solitude-payments') and request.amo_user): preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency if waffle.flag_is_active(request, 'solitude-payments'): # Now call the client. result = {} try: result = client.pay({ 'amount': amount, 'currency': currency, 'buyer': request.amo_user, 'seller': addon, 'memo': contrib_for }) except client.Error as error: # Note that by assigning this to error, it will go into the return # value for the json. General solitude errors will then be # reported back to the user. paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey: %s' % addon.pk, exc_info=True) # TODO(solitude): just use the dictionary when solitude is live. paykey = result.get('pay_key', '') status = result.get('status', '') uuid_ = result.get('uuid', '') else: # TODO(solitude): remove this when solitude goes live. try: paykey, status = paypal.get_paykey( dict(amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: # TODO(solitude): at some point we'll have to see what to do with # contributions. download_source = request.REQUEST.get('src', '') contrib = Contribution(addon_id=addon.id, amount=amount, source=download_source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user, price_tier=addon.premium.price, client_data=ClientData.get_or_create(request)) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED': contrib.type = amo.CONTRIB_PURCHASE else: log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' else: #TODO(solitude): remove this when solitude goes live. if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': str(error), 'status': status }), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(addon.get_detail_url())
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # L10n: {0} is the addon name. contrib_for = (_(u'Mozilla Marketplace purchase of {0}').format( addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. if waffle.switch_is_active('currencies'): form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency if not amount: # We won't write a contribution row for this because there # will not be a valid Paypal transaction. But we have to write the # Purchase row, something that writing to the contribution normally # does for us. AddonPurchase.objects.safer_get_or_create(addon=addon, user=request.amo_user) return http.HttpResponse(json.dumps({ 'url': '', 'paykey': '', 'error': '', 'status': 'COMPLETED' }), content_type='application/json') paykey, status, error = '', '', '' preapproval = None if request.amo_user: preapproval = request.amo_user.get_preapproval() # User the users default currency. if currency == 'USD' and preapproval and preapproval.currency: currency = preapproval.currency try: paykey, status = paypal.get_paykey( dict(amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern='purchase.done', preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=addon.app_slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({ 'url': url, 'paykey': paykey, 'error': str(error), 'status': status }), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return redirect(url) messages.success(request, _('Purchase complete')) return redirect(addon.get_detail_url())
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u'Purchase of {0}').format(jinja2.escape(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency paykey, status, error = '', '', '' preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() try: pattern = 'addons.purchase.finished' slug = addon.slug if addon.is_webapp(): pattern = 'apps.purchase.finished' slug = addon.app_slug paykey, status = paypal.get_paykey( dict(amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern=pattern, preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(shared_url('addons.detail', addon))
uuid=uuid_ )) except paypal.PaypalError, exc: paypal.paypal_log_cef(request, product, uuid_, 'in-app PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error(u'Error getting paykey, in-app payment: %s' % pay_req['_config'].pk, exc_info=True) InappPayLog.log(request, 'PAY_ERROR', config=pay_req['_config']) return render_error(request, exc) with transaction.commit_on_success(): contrib = Contribution(addon_id=product.id, amount=price, source=source, source_locale=request.LANG, currency=currency, uuid=str(uuid_), price_tier=tier, type=amo.CONTRIB_INAPP_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing in-app payment contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, product, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if waffle.flag_is_active(request, 'solitude-payments'): result = client.post_pay_check(data={'pay_key': paykey}) if result['status'] == 'COMPLETED':
def test_total_contributions(self): c = Contribution() c.addon_id = 3615 c.amount = '9.99' c.save() tasks.addon_total_contributions(3615) a = Addon.objects.no_cache().get(pk=3615) eq_(float(a.total_contributions), 9.99) c = Contribution() c.addon_id = 3615 c.amount = '10.00' c.save() tasks.addon_total_contributions(3615) a = Addon.objects.no_cache().get(pk=3615) eq_(float(a.total_contributions), 19.99)
def purchase(request, addon): log.debug('Starting purchase of addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) amount = addon.premium.get_price() source = request.POST.get('source', '') uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest() # l10n: {0} is the addon name contrib_for = _(u'Purchase of {0}').format(jinja2.escape(addon.name)) # Default is USD. amount, currency = addon.premium.get_price(), 'USD' # If tier is specified, then let's look it up. form = PriceCurrencyForm(data=request.POST, addon=addon) if form.is_valid(): tier = form.get_tier() if tier: amount, currency = tier.price, tier.currency paykey, status, error = '', '', '' preapproval = None if waffle.flag_is_active(request, 'allow-pre-auth') and request.amo_user: preapproval = request.amo_user.get_preapproval() try: pattern = 'addons.purchase.finished' slug = addon.slug if addon.is_webapp(): pattern = 'apps.purchase.finished' slug = addon.app_slug paykey, status = paypal.get_paykey(dict( amount=amount, chains=settings.PAYPAL_CHAINS, currency=currency, email=addon.paypal_id, ip=request.META.get('REMOTE_ADDR'), memo=contrib_for, pattern=pattern, preapproval=preapproval, qs={'realurl': request.POST.get('realurl')}, slug=slug, uuid=uuid_)) except paypal.PaypalError as error: paypal.paypal_log_cef(request, addon, uuid_, 'PayKey Failure', 'PAYKEYFAIL', 'There was an error getting the paykey') log.error('Error getting paykey, purchase of addon: %s' % addon.pk, exc_info=True) if paykey: contrib = Contribution(addon_id=addon.id, amount=amount, source=source, source_locale=request.LANG, uuid=str(uuid_), type=amo.CONTRIB_PENDING, paykey=paykey, user=request.amo_user) log.debug('Storing contrib for uuid: %s' % uuid_) # If this was a pre-approval, it's completed already, we'll # double check this with PayPal, just to be sure nothing went wrong. if status == 'COMPLETED': paypal.paypal_log_cef(request, addon, uuid_, 'Purchase', 'PURCHASE', 'A user purchased using pre-approval') log.debug('Status is completed for uuid: %s' % uuid_) if paypal.check_purchase(paykey) == 'COMPLETED': log.debug('Check purchase is completed for uuid: %s' % uuid_) contrib.type = amo.CONTRIB_PURCHASE else: # In this case PayPal disagreed, we should not be trusting # what get_paykey said. Which is a worry. log.error('Check purchase failed on uuid: %s' % uuid_) status = 'NOT-COMPLETED' contrib.save() else: log.error('No paykey present for uuid: %s' % uuid_) log.debug('Got paykey for addon: %s by user: %s' % (addon.pk, request.amo_user.pk)) url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.POST.get('result_type') == 'json' or request.is_ajax(): return http.HttpResponse(json.dumps({'url': url, 'paykey': paykey, 'error': str(error), 'status': status}), content_type='application/json') # This is the non-Ajax fallback. if status != 'COMPLETED': return http.HttpResponseRedirect(url) messages.success(request, _('Purchase complete')) return http.HttpResponseRedirect(shared_url('addons.detail', addon))
def contribute(request, addon): contrib_type = request.POST.get("type", "suggested") is_suggested = contrib_type == "suggested" source = request.POST.get("source", "") comment = request.POST.get("comment", "") amount = {"suggested": addon.suggested_amount, "onetime": request.POST.get("onetime-amount", "")}.get( contrib_type, "" ) if not amount: amount = settings.DEFAULT_SUGGESTED_CONTRIBUTION form = ContributionForm({"amount": amount}) if not form.is_valid(): return http.HttpResponse( json.dumps({"error": "Invalid data.", "status": "", "url": "", "paykey": ""}), content_type="application/json", ) contribution_uuid = hashlib.md5(str(uuid.uuid4())).hexdigest() if addon.charity: # TODO(andym): Figure out how to get this in the addon authors # locale, rather than the contributors locale. name, paypal_id = (u"%s: %s" % (addon.name, addon.charity.name), addon.charity.paypal) else: name, paypal_id = addon.name, addon.paypal_id # l10n: {0} is the addon name contrib_for = _(u"Contribution for {0}").format(jinja2.escape(name)) paykey, error, status = "", "", "" try: paykey, status = paypal.get_paykey( dict( amount=amount, email=paypal_id, ip=request.META.get("REMOTE_ADDR"), memo=contrib_for, pattern="addons.paypal", slug=addon.slug, uuid=contribution_uuid, ) ) except paypal.PaypalError as error: paypal.paypal_log_cef( request, addon, contribution_uuid, "PayKey Failure", "PAYKEYFAIL", "There was an error getting the paykey" ) log.error("Error getting paykey, contribution for addon: %s" % addon.pk, exc_info=True) if paykey: contrib = Contribution( addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey, ) contrib.save() url = "%s?paykey=%s" % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get("result_type") == "json" or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse( json.dumps({"url": url, "paykey": paykey, "error": str(error), "status": status}), content_type="application/json", ) return http.HttpResponseRedirect(url)
contrib_for }) except paypal.AuthError, error: paypal_log.error('Authentication error: %s' % error) nice_error = _('There was a problem communicating with Paypal.') except Exception, error: paypal_log.error('Error: %s' % error) nice_error = _('There was a problem with that contribution.') if paykey: contrib = Contribution(addon_id=addon.id, charity_id=addon.charity_id, amount=amount, source=source, source_locale=request.LANG, annoying=addon.annoying, uuid=str(contribution_uuid), is_suggested=is_suggested, suggested_amount=addon.suggested_amount, comment=comment, paykey=paykey) contrib.save() assert settings.PAYPAL_FLOW_URL, 'settings.PAYPAL_FLOW_URL is not defined' url = '%s?paykey=%s' % (settings.PAYPAL_FLOW_URL, paykey) if request.GET.get('result_type') == 'json' or request.is_ajax(): # If there was an error getting the paykey, then JSON will # not have a paykey and the JS can cope appropriately. return http.HttpResponse(json.dumps({ 'url': url,