def refund_campaign(link, camp, billable_amount, billable_impressions): refund_amount = get_refund_amount(camp, billable_amount) if refund_amount <= 0: return owner = Account._byID(camp.owner_id, data=True) try: success = authorize.refund_transaction(owner, camp.trans_id, camp._id, refund_amount) except authorize.AuthorizeNetException as e: text = "%s $%s refund failed" % (camp, refund_amount) PromotionLog.add(link, text) g.log.debug(text + " (response: %s)" % e) return text = "%s completed with $%s billable (%s impressions @ $%s)." " %s refunded." % ( camp, billable_amount, billable_impressions, camp.cpm, refund_amount, ) PromotionLog.add(link, text) camp.refund_amount = refund_amount camp._commit() unset_underdelivered_campaigns(camp) emailer.refunded_promo(link)
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ PromotionLog.add(link, 'status update: accepted') # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) if link._fullname in set(l.thing_name for l in PromotionWeights.get_campaigns(now)): PromotionLog.add(link, 'Marked promotion for acceptance') charge_pending(0) # campaign must be charged before it will go live queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now] transactions = get_transactions(link, future_campaigns) charged_campaigns = [camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged())] for campaign in charged_campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def edit_campaign(link, campaign, dates, bid, sr): sr_name = sr.name if sr else '' # empty string means target to all try: # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign) # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, sr_name, campaign.trans_id, commit=True) # record the transaction text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid) PromotionLog.add(link, text) # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) except Exception, e: # record error and rethrow g.log.error("Failed to update PromoCampaign %s on link %d. Error was: %r" % (campaign._id, link._id, e)) try: # wrapped in try/except so orig error won't be lost if commit fails text = 'update FAILED. (campaign: %s, bid: %.2f)' % (campaign._id, bid) PromotionLog.add(link, text) except: pass raise e
def refund_campaign(link, camp, refund_amount, billable_amount, billable_impressions): owner = Account._byID(camp.owner_id, data=True) success, reason = authorize.refund_transaction( owner, camp.trans_id, camp._id, refund_amount) if not success: text = ('%s $%s refund failed' % (camp, refund_amount)) PromotionLog.add(link, text) g.log.debug(text + ' (reason: %s)' % reason) return False if billable_impressions: text = ('%s completed with $%s billable (%s impressions @ $%s).' ' %s refunded.' % (camp, billable_amount, billable_impressions, camp.bid_pennies / 100., refund_amount)) else: text = ('%s completed with $%s billable. %s refunded' % (camp, billable_amount, refund_amount)) PromotionLog.add(link, text) camp.refund_amount = refund_amount camp._commit() queries.unset_underdelivered_campaigns(camp) emailer.refunded_promo(link) return True
def terminate_campaign(link, campaign): if not is_live_promo(link, campaign): return now = promo_datetime_now() original_end = campaign.end_date dates = [campaign.start_date, now] # NOTE: this will delete PromotionWeights after and including now.date() edit_campaign( link=link, campaign=campaign, dates=dates, target=campaign.target, frequency_cap=campaign.frequency_cap, priority=campaign.priority, location=campaign.location, total_budget_pennies=campaign.total_budget_pennies, cost_basis=campaign.cost_basis, bid_pennies=campaign.bid_pennies, ) campaigns = list(PromoCampaign._by_link(link._id)) is_live = any(is_live_promo(link, camp) for camp in campaigns if camp._id != campaign._id) if not is_live: update_promote_status(link, PROMOTE_STATUS.finished) all_live_promo_srnames(_update=True) msg = 'terminated campaign %s (original end %s)' % (campaign._id, original_end.date()) PromotionLog.add(link, msg)
def new_promotion(title, url, selftext, user, ip): """ Creates a new promotion with the provided title, etc, and sets it status to be 'unpaid'. """ sr = Subreddit._byID(get_promote_srid()) l = Link._submit(title, url, user, sr, ip) l.promoted = True l.disable_comments = False PromotionLog.add(l, 'promotion created') if url == 'self': l.url = l.make_permalink_slow() l.is_self = True l.selftext = selftext l._commit() # set the status of the link, populating the query queue if c.user_is_sponsor or user.trusted_sponsor: set_promote_status(l, PROMOTE_STATUS.accepted) else: set_promote_status(l, PROMOTE_STATUS.unpaid) # the user has posted a promotion, so enable the promote menu unless # they have already opted out if user.pref_show_promote is not False: user.pref_show_promote = True user._commit() # notify of new promo emailer.new_promo(l) return l
def charge_campaign(link, campaign): if charged_or_not_needed(campaign): return user = Account._byID(link.author_id) success, reason = authorize.charge_transaction(user, campaign.trans_id, campaign._id) if not success: if reason == authorize.TRANSACTION_NOT_FOUND: # authorization hold has expired original_trans_id = campaign.trans_id campaign.trans_id = NO_TRANSACTION campaign._commit() text = ('voided expired transaction for %s: (trans_id: %d)' % (campaign, original_trans_id)) PromotionLog.add(link, text) return hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign) if not is_promoted(link): update_promote_status(link, PROMOTE_STATUS.pending) emailer.queue_promo(link, campaign.bid, campaign.trans_id) text = ('auth charge for campaign %s, trans_id: %d' % (campaign._id, campaign.trans_id)) PromotionLog.add(link, text)
def new_promotion(is_self, title, content, author, ip): """ Creates a new promotion with the provided title, etc, and sets it status to be 'unpaid'. """ sr = Subreddit._byID(Subreddit.get_promote_srid()) l = Link._submit( is_self=is_self, title=title, content=content, author=author, sr=sr, ip=ip, ) l.promoted = True l.disable_comments = False l.sendreplies = True PromotionLog.add(l, 'promotion created') update_promote_status(l, PROMOTE_STATUS.unpaid) # the user has posted a promotion, so enable the promote menu unless # they have already opted out if author.pref_show_promote is not False: author.pref_show_promote = True author._commit() # notify of new promo emailer.new_promo(l) return l
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) promotion_weights = PromotionWeights.get_campaigns(now) live_campaigns = {pw.promo_idx for pw in promotion_weights if pw.thing_name == link._fullname} if live_campaigns: campaigns = PromoCampaign._byID(live_campaigns, data=True, return_dict=False) PromotionLog.add(link, "has live campaigns, forcing live") charge_pending(0) # campaign must be charged before it will go live for campaign in campaigns: hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now] transactions = get_transactions(link, future_campaigns) charged_campaigns = [ camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged()) ] for campaign in charged_campaigns: hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def new_promotion(title, url, selftext, user, ip): """ Creates a new promotion with the provided title, etc, and sets it status to be 'unpaid'. """ sr = Subreddit._byID(get_promote_srid()) l = Link._submit(title, url, user, sr, ip) l.promoted = True l.disable_comments = False PromotionLog.add(l, "promotion created") if url == "self": l.url = l.make_permalink_slow() l.is_self = True l.selftext = selftext l._commit() update_promote_status(l, PROMOTE_STATUS.unpaid) # the user has posted a promotion, so enable the promote menu unless # they have already opted out if user.pref_show_promote is not False: user.pref_show_promote = True user._commit() # notify of new promo emailer.new_promo(l) return l
def _deactivate_campaign(link, campaign): with g.make_lock('adzerk_update', 'adzerk-' + link._fullname): g.log.debug('running deactivate_campaign %s' % link) az_flight = update_flight(link, campaign) az_flight.IsActive = False az_flight._send() PromotionLog.add(link, 'deactivated %s' % az_flight)
def update_campaign(link): """Add/update a reddit link as an Adzerk Campaign""" if hasattr(link, 'adzerk_campaign_id'): az_campaign = adzerk_api.Campaign.get(link.adzerk_campaign_id) else: az_campaign = None d = { 'AdvertiserId': g.az_selfserve_advertiser_id, 'IsDeleted': False, 'IsActive': not link._deleted, 'Price': 0, } log_text = None if az_campaign: changed = update_changed(az_campaign, **d) else: d.update({ 'Name': link._fullname, 'Flights': [], 'StartDate': date_to_adzerk(datetime.datetime.now(g.tz)), }) az_campaign = adzerk_api.Campaign.create(**d) link.adzerk_campaign_id = az_campaign.Id link._commit() log_text = 'created %s' % az_campaign if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) return az_campaign
def create_cfmap(link, campaign, az_campaign, az_creative, az_flight): """Create a CreativeFlightMap. Map the the reddit link (adzerk Creative) and reddit campaign (adzerk Flight). """ if getattr(campaign, 'adzerk_cfmap_id', None) is not None: raise AttributeError('%s has existing adzerk_cfmap_id' % campaign) d = { 'SizeOverride': False, 'CampaignId': az_campaign.Id, 'PublisherAccountId': g.az_selfserve_advertiser_id, 'Percentage': 100, # Each flight only has one creative (what about autobalanced) 'DistributionType': 2, # 2: Percentage, 1: Auto-Balanced, 0: ??? 'Iframe': False, 'Creative': {'Id': az_creative.Id}, 'FlightId': az_flight.Id, 'Impressions': 100, # Percentage 'IsDeleted': False, 'IsActive': True, } az_cfmap = adzerk_api.CreativeFlightMap.create(az_flight.Id, **d) campaign.adzerk_cfmap_id = az_cfmap.Id campaign._commit() log_text = 'created %s' % az_cfmap PromotionLog.add(link, log_text) g.log.info(log_text) return az_cfmap
def _deactivate_overdelivered(link, campaign): with g.make_lock('adzerk_update', 'adzerk-' + link._fullname): msg = '%s deactivating adzerk flight for %s - %s' g.log.info(msg % (datetime.datetime.now(g.tz), link, campaign)) az_flight = update_flight(link, campaign) PromotionLog.add(link, 'deactivated %s' % az_flight)
def edit_campaign(link, campaign, dates, bid, cpm, sr, priority): sr_name = sr.name if sr else '' # empty string means target to all # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign) # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, cpm, sr_name, campaign.trans_id, priority, commit=True) if campaign.priority.cpm: # record the transaction text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid) PromotionLog.add(link, text) # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def charge_pending(offset=1): for l, camp, weight in accepted_campaigns(offset=offset): user = Account._byID(l.author_id) try: if charged_or_not_needed(camp): continue charge_succeeded = authorize.charge_transaction(user, camp.trans_id, camp._id) if not charge_succeeded: continue hooks.get_hook('promote.new_charge').call(link=l, campaign=camp) if is_promoted(l): emailer.queue_promo(l, camp.bid, camp.trans_id) else: set_promote_status(l, PROMOTE_STATUS.pending) emailer.queue_promo(l, camp.bid, camp.trans_id) text = ('auth charge for campaign %s, trans_id: %d' % (camp._id, camp.trans_id)) PromotionLog.add(l, text) except: print "Error on %s, campaign %s" % (l, camp._id)
def update_creative(link, az_advertiser): """Add/update a reddit link as an Adzerk Creative""" if getattr(link, 'external_creative_id', None) is not None: az_creative = adzerk_api.Creative.get(link.external_creative_id) else: az_creative = None title = link._fullname url = add_sr(link.url, sr_path=False) if link.is_self else link.url # protocols are case sensitive (lower) in adzerk. # can cause double protocols: # http://Http://www.example.com url = re.sub(r"^(https?)", lambda m: m.group(0).lower(), url, flags=re.I) # as long as there are no 3rd party trackers for the link # it's DNT compliant. DNT_compliant = (not (hasattr(link, 'third_party_tracking_url') or hasattr(link, 'third_party_tracking_url_2'))) d = { 'Body': title, 'ScriptBody': render_link(link), 'AdTypeId': LEADERBOARD_AD_TYPE, 'Alt': '', 'Url': url, 'IsHTMLJS': True, 'IsSync': False, 'IsDeleted': False, 'IsActive': not link._deleted, 'IsNoTrack': DNT_compliant, } if az_creative: changed = update_changed(az_creative, **d) change_strs = make_change_strings(changed) if change_strs: log_text = 'updated %s: ' % az_creative + ', '.join(change_strs) else: log_text = None else: d.update({ 'AdvertiserId': az_advertiser.Id, 'Title': title, }) try: az_creative = adzerk_api.Creative.create(**d) except: raise ValueError(d) link.external_creative_id = az_creative.Id link._commit() log_text = 'created %s' % az_creative if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) return az_creative
def toggle_pause_campaign(link, campaign, should_pause): campaign.paused = should_pause campaign._commit() action = "paused" if should_pause else "resumed" PromotionLog.add(link, "%s campaign %s" % (action, campaign._id)) hooks.get_hook("promote.edit_campaign").call(link=link, campaign=campaign)
def review_fraud(link, is_fraud): link.fraud = is_fraud link._commit() PromotionLog.add(link, "marked as fraud" if is_fraud else "resolved as not fraud") queries.unset_payment_flagged_link(link) if is_fraud: reject_promotion(link, "fraud") hooks.get_hook("promote.fraud_identified").call(link=link, sponsor=c.user)
def flag_payment(link, reason): # already determined to be fraud. if link.payment_flagged_reason and link.fraud: return link.payment_flagged_reason = reason link._commit() PromotionLog.add(link, "payment flagged: %s" % reason) queries.set_payment_flagged_link(link)
def update_adzerk(link, campaign): az_campaign = update_campaign(link) az_creative = update_creative(link, campaign) az_flight = update_flight(link, campaign) az_cfmap = update_cfmap(link, campaign) text = ('%s/%s updated to %s, %s, %s, %s' % (link, campaign, az_campaign, az_creative, az_flight, az_cfmap)) PromotionLog.add(link, text)
def toggle_pause_campaign(link, campaign, should_pause): campaign.paused = should_pause campaign._commit() action = 'paused' if should_pause else 'resumed' PromotionLog.add(link, '%s campaign %s' % (action, campaign._id)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def new_campaign(link, dates, bid, sr): # empty string for sr_name means target to all sr_name = sr.name if sr else "" campaign = PromoCampaign._new(link, sr_name, bid, dates[0], dates[1]) PromotionWeights.add(link, campaign._id, sr_name, dates[0], dates[1], bid) PromotionLog.add(link, 'campaign %s created' % campaign._id) author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) return campaign
def void_campaign(link, campaign): transactions = get_transactions(link, [campaign]) bid_record = transactions.get(campaign._id) if bid_record: a = Account._byID(link.author_id) authorize.void_transaction(a, bid_record.transaction, campaign._id) campaign.trans_id = NO_TRANSACTION campaign._commit() text = "voided transaction for %s: (trans_id: %d)" % (campaign, bid_record.transaction) PromotionLog.add(link, text)
def update_cfmap(link, campaign, triggered_by): """Create a CreativeFlightMap. Map the the reddit link (adzerk Creative) and reddit campaign (adzerk Flight). """ if getattr(campaign, 'external_cfmap_id', None) is not None: return d = { 'SizeOverride': False, 'CampaignId': link.external_campaign_id, 'Percentage': 100, # Each flight only has one creative (what about autobalanced) 'DistributionType': 2, # 2: Percentage, 1: Auto-Balanced, 0: ??? 'Iframe': False, 'Creative': {'Id': link.external_creative_id}, 'FlightId': campaign.external_flight_id, 'Impressions': 100, # Percentage 'IsDeleted': False, 'IsActive': True, } request_error = None try: az_cfmap = adzerk_api.CreativeFlightMap.create(campaign.external_flight_id, **d) except adzerk_api.AdzerkError as e: request_error = e finally: g.ad_events.adzerk_api_request( request_type="create_cfmap", thing=campaign, request_body=d, triggered_by=triggered_by, request_error=request_error, additional_data=dict( link_fullname=link._fullname, link_id=link._id, ) ) if request_error: raise request_error campaign.external_cfmap_id = az_cfmap.Id campaign._commit() log_text = 'created %s' % az_cfmap PromotionLog.add(link, log_text) g.log.info(log_text) return az_cfmap
def edit_campaign(link, campaign, dates, bid, cpm, target, priority, location, platform='desktop', mobile_os=None): changed = {} if bid != campaign.bid: # if the bid amount changed, cancel any pending transactions void_campaign(link, campaign, reason='changed_bid') changed['bid'] = ("$%0.2f" % campaign.bid, "$%0.2f" % bid) hooks.get_hook('promote.edit_bid').call( link=link,campaign=campaign, previous=campaign.bid, current=bid) campaign.bid = bid if dates[0] != campaign.start_date or dates[1] != campaign.end_date: original = '%s to %s' % (campaign.start_date, campaign.end_date) edited = '%s to %s' % (dates[0], dates[1]) changed['dates'] = (original, edited) campaign.start_date = dates[0] campaign.end_date = dates[1] if cpm != campaign.cpm: changed['cpm'] = (campaign.cpm, cpm) campaign.cpm = cpm if target != campaign.target: changed['target'] = (campaign.target, target) campaign.target = target if priority != campaign.priority: changed['priority'] = (campaign.priority.name, priority.name) campaign.priority = priority if location != campaign.location: changed['location'] = (campaign.location, location) campaign.location = location if platform != campaign.platform: changed["platform"] = (campaign.platform, platform) campaign.platform = platform if mobile_os != campaign.mobile_os: changed["mobile_os"] = (campaign.mobile_os, mobile_os) campaign.mobile_os = mobile_os change_strs = map(lambda t: '%s: %s -> %s' % (t[0], t[1][0], t[1][1]), changed.iteritems()) change_text = ', '.join(change_strs) campaign._commit() # update the index PromotionWeights.reschedule(link, campaign) if campaign.priority.cpm: # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) # record the changes if change_text: PromotionLog.add(link, 'edited %s: %s' % (campaign, change_text)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def update_flight(link, campaign): """Add/update a reddit campaign as an Adzerk Flight""" if hasattr(campaign, 'adzerk_flight_id'): az_flight = adzerk_api.Flight.get(campaign.adzerk_flight_id) else: az_flight = None az_campaign = adzerk_api.Campaign.get(link.adzerk_campaign_id) d = { 'StartDate': date_to_adzerk(campaign.start_date), 'EndDate': date_to_adzerk(campaign.end_date), 'OptionType': 1, # 1: CPM, 2: Remainder 'IsUnlimited': False, 'IsFullSpeed': False, 'Keywords': srname_to_keyword(campaign.sr_name), 'CampaignId': az_campaign.Id, 'PriorityId': g.az_selfserve_priority_id, # TODO: property of PromoCampaign 'IsDeleted': False, 'IsActive': not campaign._deleted, 'IsFreqCap': None, } is_cpm = hasattr(campaign, 'cpm') if is_cpm: d.update({ 'Price': campaign.cpm / 100., # convert from cents to dollars 'Impressions': campaign.impressions + ADZERK_IMPRESSION_BUMP, 'GoalType': 1, # 1: Impressions 'RateType': 2, # 2: CPM }) else: d.update({ 'Price': campaign.bid, 'Impressions': int(campaign.bid / campaign.ndays), 'GoalType': 2, # 2: Percentage 'RateType': 1, # 1: Flat }) log_text = None if az_flight: changed = update_changed(az_flight, **d) else: d.update({'Name': campaign._fullname}) az_flight = adzerk_api.Flight.create(**d) campaign.adzerk_flight_id = az_flight.Id campaign._commit() log_text = 'created %s' % az_flight if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) return az_flight
def new_campaign(link, dates, bid, cpm, target, priority, location, platform, mobile_os): campaign = PromoCampaign.create(link, target, bid, cpm, dates[0], dates[1], priority, location, platform, mobile_os) PromotionWeights.add(link, campaign) PromotionLog.add(link, "campaign %s created" % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook("promote.new_campaign").call(link=link, campaign=campaign) return campaign
def finalize_completed_campaigns(daysago=1): # PromoCampaign.end_date is utc datetime with year, month, day only now = datetime.datetime.now(g.tz) date = now - datetime.timedelta(days=daysago) date = date.replace(hour=0, minute=0, second=0, microsecond=0) q = PromoCampaign._query( PromoCampaign.c.end_date == date, # exclude no transaction PromoCampaign.c.trans_id != NO_TRANSACTION, data=True) # filter out freebies campaigns = filter(lambda camp: camp.trans_id > NO_TRANSACTION, q) if not campaigns: return # check that traffic is up to date earliest_campaign = min(campaigns, key=lambda camp: camp.start_date) start, end = get_total_run(earliest_campaign) missing_traffic = traffic.get_missing_traffic(start.replace(tzinfo=None), date.replace(tzinfo=None)) if missing_traffic: raise ValueError("Can't finalize campaigns finished on %s." "Missing traffic from %s" % (date, missing_traffic)) links = Link._byID([camp.link_id for camp in campaigns], data=True) underdelivered_campaigns = [] for camp in campaigns: if hasattr(camp, 'refund_amount'): continue link = links[camp.link_id] billable_impressions = get_billable_impressions(camp) billable_amount = get_billable_amount(camp, billable_impressions) if billable_amount >= camp.total_budget_pennies: if hasattr(camp, 'cpm'): text = '%s completed with $%s billable (%s impressions @ $%s).' text %= (camp, billable_amount, billable_impressions, camp.bid_dollars) else: text = '%s completed with $%s billable (pre-CPM).' text %= (camp, billable_amount) PromotionLog.add(link, text) camp.refund_amount = 0. camp._commit() elif charged_or_not_needed(camp): underdelivered_campaigns.append(camp) if underdelivered_campaigns: queries.set_underdelivered_campaigns(underdelivered_campaigns)
def reject_promotion(link, reason=None): was_live = is_promoted(link) update_promote_status(link, PROMOTE_STATUS.rejected) if reason: PromotionLog.add(link, "rejected: %s" % reason) # Send a rejection email (unless the advertiser requested the reject) if not c.user or c.user._id != link.author_id: emailer.reject_promo(link, reason=reason) if was_live: all_live_promo_srnames(_update=True)
def edit_campaign(link, campaign, dates, bid, cpm, sr, priority, location): sr_name = sr.name if sr else '' # empty string means target to all changed = {} if bid != campaign.bid: changed['bid'] = ("$%0.2f" % campaign.bid, "$%0.2f" % bid) if dates[0] != campaign.start_date or dates[1] != campaign.end_date: original = '%s to %s' % (campaign.start_date, campaign.end_date) edited = '%s to %s' % (dates[0], dates[1]) changed['dates'] = (original, edited) if cpm != campaign.cpm: changed['cpm'] = (campaign.cpm, cpm) if sr_name != campaign.sr_name: format_sr_name = (lambda sr_name: '/r/%s' % sr_name if sr_name else '<frontpage>') changed['sr_name'] = map(format_sr_name, (campaign.sr_name, sr_name)) if priority != campaign.priority: changed['priority'] = (campaign.priority.name, priority.name) change_strs = map(lambda t: '%s: %s -> %s' % (t[0], t[1][0], t[1][1]), changed.iteritems()) change_text = ', '.join(change_strs) # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign, reason='changed_bid') # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, cpm, sr_name, campaign.trans_id, priority, location, commit=True) if campaign.priority.cpm: # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) # record the changes if change_text: PromotionLog.add(link, 'edited %s: %s' % (campaign, change_text)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def finalize_completed_campaigns(daysago=1): # PromoCampaign.end_date is utc datetime with year, month, day only now = datetime.datetime.now(g.tz) date = now - datetime.timedelta(days=daysago) date = date.replace(hour=0, minute=0, second=0, microsecond=0) q = PromoCampaign._query(PromoCampaign.c.end_date == date, # exclude no transaction PromoCampaign.c.trans_id != NO_TRANSACTION, data=True) # filter out freebies campaigns = filter(lambda camp: camp.trans_id > NO_TRANSACTION, q) if not campaigns: return # check that traffic is up to date earliest_campaign = min(campaigns, key=lambda camp: camp.start_date) start, end = get_total_run(earliest_campaign) missing_traffic = traffic.get_missing_traffic(start.replace(tzinfo=None), date.replace(tzinfo=None)) if missing_traffic: raise ValueError("Can't finalize campaigns finished on %s." "Missing traffic from %s" % (date, missing_traffic)) links = Link._byID([camp.link_id for camp in campaigns], data=True) underdelivered_campaigns = [] for camp in campaigns: if hasattr(camp, 'refund_amount'): continue link = links[camp.link_id] billable_impressions = get_billable_impressions(camp) billable_amount = get_billable_amount(camp, billable_impressions) if billable_amount >= camp.total_budget_pennies: if hasattr(camp, 'cpm'): text = '%s completed with $%s billable (%s impressions @ $%s).' text %= (camp, billable_amount, billable_impressions, camp.bid_dollars) else: text = '%s completed with $%s billable (pre-CPM).' text %= (camp, billable_amount) PromotionLog.add(link, text) camp.refund_amount = 0. camp._commit() elif charged_or_not_needed(camp): underdelivered_campaigns.append(camp) if underdelivered_campaigns: queries.set_underdelivered_campaigns(underdelivered_campaigns)
def new_campaign(link, dates, bid, cpm, target, priority, location): campaign = PromoCampaign.create(link, target, bid, cpm, dates[0], dates[1], priority, location) PromotionWeights.add(link, campaign) PromotionLog.add(link, 'campaign %s created' % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('promote.new_campaign').call(link=link, campaign=campaign) return campaign
def flag_payment(link, reason): # already determined to be fraud or already flagged for that reason. if link.fraud or reason in link.payment_flagged_reason: return if link.payment_flagged_reason: link.payment_flagged_reason += (", %s" % reason) else: link.payment_flagged_reason = reason link._commit() PromotionLog.add(link, "payment flagged: %s" % reason) queries.set_payment_flagged_link(link)
def _run(msgs, chan): items = [json.loads(msg.body) for msg in msgs] if QUEUE_ALL in items: # QUEUE_ALL is just an indicator to run make_daily_promotions. # There's no promotion log to update in this case. print "Received %s QUEUE_ALL message(s)" % items.count(QUEUE_ALL) items = [i for i in items if i != QUEUE_ALL] make_daily_promotions() links = Link._by_fullname([i["link"] for i in items]) for item in items: PromotionLog.add(links[item['link']], "Finished remaking current promotions (this link " "was: %(message)s" % item)
def edit_campaign(link, campaign, dates, bid, cpm, target, priority, location): changed = {} if bid != campaign.bid: # if the bid amount changed, cancel any pending transactions void_campaign(link, campaign, reason='changed_bid') changed['bid'] = ("$%0.2f" % campaign.bid, "$%0.2f" % bid) hooks.get_hook('promote.edit_bid').call(link=link, campaign=campaign, previous=campaign.bid, current=bid) campaign.bid = bid if dates[0] != campaign.start_date or dates[1] != campaign.end_date: original = '%s to %s' % (campaign.start_date, campaign.end_date) edited = '%s to %s' % (dates[0], dates[1]) changed['dates'] = (original, edited) campaign.start_date = dates[0] campaign.end_date = dates[1] if cpm != campaign.cpm: changed['cpm'] = (campaign.cpm, cpm) campaign.cpm = cpm if target != campaign.target: changed['target'] = (campaign.target, target) campaign.target = target if priority != campaign.priority: changed['priority'] = (campaign.priority.name, priority.name) campaign.priority = priority if location != campaign.location: changed['location'] = (campaign.location, location) campaign.location = location change_strs = map(lambda t: '%s: %s -> %s' % (t[0], t[1][0], t[1][1]), changed.iteritems()) change_text = ', '.join(change_strs) campaign._commit() # update the index PromotionWeights.reschedule(link, campaign) if campaign.priority.cpm: # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) # record the changes if change_text: PromotionLog.add(link, 'edited %s: %s' % (campaign, change_text)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def new_campaign(link, dates, bid, cpm, sr, priority, location): # empty string for sr_name means target to all sr_name = sr.name if sr else "" campaign = PromoCampaign._new(link, sr_name, bid, cpm, dates[0], dates[1], priority, location) PromotionWeights.add(link, campaign._id, sr_name, dates[0], dates[1], bid) PromotionLog.add(link, 'campaign %s created' % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('promote.new_campaign').call(link=link, campaign=campaign) return campaign
def void_campaign(link, campaign, reason): transactions = get_transactions(link, [campaign]) bid_record = transactions.get(campaign._id) if bid_record: a = Account._byID(link.author_id) authorize.void_transaction(a, bid_record.transaction, campaign._id) campaign.trans_id = NO_TRANSACTION campaign._commit() text = ('voided transaction for %s: (trans_id: %d)' % (campaign, bid_record.transaction)) PromotionLog.add(link, text) if bid_record.transaction > 0: # notify the user that the transaction was voided if it was not # a freebie emailer.void_payment(link, campaign, reason)
def update_cfmap(link, campaign): """Add/update a CreativeFlightMap. Map the the reddit link (adzerk Creative) and reddit campaign (adzerk Flight). """ az_campaign = adzerk_api.Campaign.get(link.adzerk_campaign_id) az_creative = adzerk_api.Creative.get(campaign.adzerk_creative_id) az_flight = adzerk_api.Flight.get(campaign.adzerk_flight_id) if hasattr(campaign, 'adzerk_cfmap_id'): az_cfmap = adzerk_api.CreativeFlightMap.get(az_flight.Id, campaign.adzerk_cfmap_id) else: az_cfmap = None d = { 'SizeOverride': False, 'CampaignId': az_campaign.Id, 'PublisherAccountId': g.az_selfserve_advertiser_id, 'Percentage': 100, # Each flight only has one creative (what about autobalanced) 'DistributionType': 2, # 2: Percentage, 1: Auto-Balanced, 0: ??? 'Iframe': False, 'Creative': {'Id': az_creative.Id}, 'FlightId': az_flight.Id, 'Impressions': 100, # Percentage 'IsDeleted': False, 'IsActive': not campaign._deleted, } log_text = None if az_cfmap: changed = update_changed(az_cfmap, **d) else: az_cfmap = adzerk_api.CreativeFlightMap.create(az_flight.Id, **d) campaign.adzerk_cfmap_id = az_cfmap.Id campaign._commit() log_text = 'created %s' % az_cfmap if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) return az_cfmap
def reject_promotion(link, reason=None): PromotionLog.add(link, 'status update: rejected') # update the query queue # Since status is updated first, # if make_daily_promotions happens to run # while we're doing work here, it will correctly exclude it set_promote_status(link, PROMOTE_STATUS.rejected) all_ads = get_live_promotions([LiveAdWeights.ALL_ADS]) links = set(x.link for x in all_ads[LiveAdWeights.ALL_ADS]) if link._fullname in links: PromotionLog.add(link, 'Marked promotion for rejection') queue_changed_promo(link, "rejected") # Send a rejection email (unless the advertiser requested the reject) if not c.user or c.user._id != link.author_id: emailer.reject_promo(link, reason=reason)
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) promotion_weights = PromotionWeights.get_campaigns(now) live_campaigns = { pw.promo_idx for pw in promotion_weights if pw.thing_name == link._fullname } if live_campaigns: campaigns = PromoCampaign._byID(live_campaigns, data=True, return_dict=False) PromotionLog.add(link, 'has live campaigns, forcing live') charge_pending(0) # campaign must be charged before it will go live for campaign in campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [ camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now ] transactions = get_transactions(link, future_campaigns) charged_campaigns = [ camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged()) ] for campaign in charged_campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def charge_pending(offset=1): for l, camp, weight in accepted_campaigns(offset=offset): user = Account._byID(l.author_id) try: if (authorize.is_charged_transaction(camp.trans_id, camp._id) or not authorize.charge_transaction(user, camp.trans_id, camp._id)): continue if is_promoted(l): emailer.queue_promo(l, camp.bid, camp.trans_id) else: set_promote_status(l, PROMOTE_STATUS.pending) emailer.queue_promo(l, camp.bid, camp.trans_id) text = ('auth charge for campaign %s, trans_id: %d' % (camp._id, camp.trans_id)) PromotionLog.add(l, text) except: print "Error on %s, campaign %s" % (l, camp._id)
def new_campaign(link, dates, target, frequency_cap, priority, location, platform, mobile_os, ios_devices, ios_version_range, android_devices, android_version_range, total_budget_pennies, cost_basis, bid_pennies): campaign = PromoCampaign.create( link, target, dates[0], dates[1], frequency_cap, priority, location, platform, mobile_os, ios_devices, ios_version_range, android_devices, android_version_range, total_budget_pennies, cost_basis, bid_pennies) PromotionWeights.add(link, campaign) PromotionLog.add(link, 'campaign %s created' % campaign._id) if not campaign.is_house: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('promote.new_campaign').call(link=link, campaign=campaign) return campaign
def _update_adzerk(link, campaign): with g.make_lock('adzerk_update', 'adzerk-' + link._fullname): msg = '%s updating/creating adzerk objects for %s - %s' g.log.info(msg % (datetime.datetime.now(g.tz), link, campaign)) az_campaign = update_campaign(link) if campaign: az_creative = update_creative(link, campaign) az_flight = update_flight(link, campaign, az_campaign) if getattr(campaign, 'adzerk_cfmap_id', None) is not None: az_cfmap = adzerk_api.CreativeFlightMap.get( az_flight.Id, campaign.adzerk_cfmap_id) else: az_cfmap = create_cfmap(link, campaign, az_campaign, az_creative, az_flight) PromotionLog.add(link, 'updated %s' % az_flight) else: PromotionLog.add(link, 'updated %s' % az_campaign)
def update_creative(link, campaign): """Add/update a reddit link/campaign as an Adzerk Creative""" if getattr(campaign, 'adzerk_creative_id', None) is not None: az_creative = adzerk_api.Creative.get(campaign.adzerk_creative_id) else: az_creative = None title = '-'.join((link._fullname, campaign._fullname)) d = { 'Body': title, 'ScriptBody': render_link(link, campaign), 'AdvertiserId': g.az_selfserve_advertiser_id, 'AdTypeId': g.az_selfserve_ad_type, 'Alt': '', 'Url': '', 'IsHTMLJS': True, 'IsSync': False, 'IsDeleted': False, 'IsActive': not campaign._deleted, } if az_creative: changed = update_changed(az_creative, **d) change_strs = make_change_strings(changed) if change_strs: log_text = 'updated %s: ' % az_creative + ', '.join(change_strs) else: log_text = None else: d.update({'Title': title}) try: az_creative = adzerk_api.Creative.create(**d) except: raise ValueError(d) campaign.adzerk_creative_id = az_creative.Id campaign._commit() log_text = 'created %s' % az_creative if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) return az_creative
def edit_campaign(link, campaign, dates, bid, cpm, sr, priority): sr_name = sr.name if sr else '' # empty string means target to all try: # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign) # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, cpm, sr_name, campaign.trans_id, priority, commit=True) if campaign.priority.cpm: # record the transaction text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid) PromotionLog.add(link, text) # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) except Exception, e: # record error and rethrow g.log.error( "Failed to update PromoCampaign %s on link %d. Error was: %r" % (campaign._id, link._id, e)) try: # wrapped in try/except so orig error won't be lost if commit fails text = 'update FAILED. (campaign: %s, bid: %.2f)' % (campaign._id, bid) PromotionLog.add(link, text) except: pass raise e
def terminate_campaign(link, campaign): if not is_live_promo(link, campaign): return now = promo_datetime_now() original_end = campaign.end_date dates = [campaign.start_date, now] sr = Subreddit._by_name(campaign.sr_name) if campaign.sr_name else None # NOTE: this will delete PromotionWeights after and including now.date() edit_campaign(link, campaign, dates, campaign.bid, campaign.cpm, sr, campaign.priority, campaign.location) update_promote_status(link, PROMOTE_STATUS.finished) all_live_promo_srnames(_update=True) msg = 'terminated campaign %s (original end %s)' % (campaign._id, original_end.date()) PromotionLog.add(link, msg)
def charge_campaign(link, campaign): if charged_or_not_needed(campaign): return user = Account._byID(link.author_id) charge_succeeded = authorize.charge_transaction(user, campaign.trans_id, campaign._id) if not charge_succeeded: return hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign) if not is_promoted(link): update_promote_status(link, PROMOTE_STATUS.pending) emailer.queue_promo(link, campaign.bid, campaign.trans_id) text = ('auth charge for campaign %s, trans_id: %d' % (campaign._id, campaign.trans_id)) PromotionLog.add(link, text)
def update_campaign(link, az_advertiser=None): """Add/update a reddit link as an Adzerk Campaign""" if getattr(link, 'external_campaign_id', None) is not None: az_campaign = adzerk_api.Campaign.get(link.external_campaign_id) else: az_campaign = None d = { 'SalespersonId': g.az_selfserve_salesperson_id, 'IsDeleted': False, # deleting an adzerk object will make it # unretrievable, so just set it inactive 'IsActive': promote.is_accepted(link) and not link._deleted, 'Price': 0, } if az_advertiser: d["AdvertiserId"] = az_advertiser.Id if az_campaign: changed = update_changed(az_campaign, **d) change_strs = make_change_strings(changed) if change_strs: log_text = 'updated %s: ' % az_campaign + ', '.join(change_strs) else: log_text = None else: d.update({ 'Name': link._fullname, 'Flights': [], 'StartDate': date_to_adzerk(datetime.datetime.now(g.tz)), }) az_campaign = adzerk_api.Campaign.create(**d) link.external_campaign_id = az_campaign.Id link._commit() log_text = 'created %s' % az_campaign if log_text: PromotionLog.add(link, log_text) g.log.info(log_text) return az_campaign
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ PromotionLog.add(link, 'status update: accepted') # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) now = promo_datetime_now(0) if link._fullname in set(l.thing_name for l in PromotionWeights.get_campaigns(now)): PromotionLog.add(link, 'Marked promotion for acceptance') charge_pending(0) # campaign must be charged before it will go live queue_changed_promo(link, "accepted") if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def refund_campaign(link, camp, billable_amount): refund_amount = camp.bid - billable_amount owner = Account._byID(camp.owner_id, data=True) try: success = authorize.refund_transaction(user, camp.trans_id, camp._id, refund_amount) except authorize.AuthorizeNetException as e: text = ('%s $%s refund failed' % (camp, refund_amount)) PromotionLog.add(link, text) g.log.debug(text + ' (response: %s)' % e) return text = ( '%s completed with $%s billable (%s impressions @ $%s).' ' %s refunded.' % (camp, billable_amount, billable_impressions, camp.cpm, refund_amount)) PromotionLog.add(link, text) camp.refund_amount = refund_amount camp._commit() unset_underdelivered_campaigns(camp) emailer.refunded_promo(link)
def terminate_campaign(link, campaign): if not is_live_promo(link, campaign): return now = promo_datetime_now() original_end = campaign.end_date dates = [campaign.start_date, now] # NOTE: this will delete PromotionWeights after and including now.date() edit_campaign(link, campaign, dates, campaign.bid, campaign.cpm, campaign.target, campaign.priority, campaign.location) campaigns = list(PromoCampaign._by_link(link._id)) is_live = any(is_live_promo(link, camp) for camp in campaigns if camp._id != campaign._id) if not is_live: update_promote_status(link, PROMOTE_STATUS.finished) all_live_promo_srnames(_update=True) msg = 'terminated campaign %s (original end %s)' % (campaign._id, original_end.date()) PromotionLog.add(link, msg)
def refund_campaign(link, camp, billable_amount, billable_impressions): refund_amount = get_refund_amount(camp, billable_amount) if refund_amount <= 0: return owner = Account._byID(camp.owner_id, data=True) success, reason = authorize.refund_transaction(owner, camp.trans_id, camp._id, refund_amount) if not success: text = ('%s $%s refund failed' % (camp, refund_amount)) PromotionLog.add(link, text) g.log.debug(text + ' (reason: %s)' % reason) return text = ( '%s completed with $%s billable (%s impressions @ $%s).' ' %s refunded.' % (camp, billable_amount, billable_impressions, camp.cpm, refund_amount)) PromotionLog.add(link, text) camp.refund_amount = refund_amount camp._commit() queries.unset_underdelivered_campaigns(camp) emailer.refunded_promo(link)
def create_cfmap(link, campaign, az_campaign, az_creative, az_flight): """Create a CreativeFlightMap. Map the the reddit link (adzerk Creative) and reddit campaign (adzerk Flight). """ if getattr(campaign, 'adzerk_cfmap_id', None) is not None: raise AttributeError('%s has existing adzerk_cfmap_id' % campaign) d = { 'SizeOverride': False, 'CampaignId': az_campaign.Id, 'PublisherAccountId': g.az_selfserve_advertiser_id, 'Percentage': 100, # Each flight only has one creative (what about autobalanced) 'DistributionType': 2, # 2: Percentage, 1: Auto-Balanced, 0: ??? 'Iframe': False, 'Creative': { 'Id': az_creative.Id }, 'FlightId': az_flight.Id, 'Impressions': 100, # Percentage 'IsDeleted': False, 'IsActive': True, } az_cfmap = adzerk_api.CreativeFlightMap.create(az_flight.Id, **d) campaign.adzerk_cfmap_id = az_cfmap.Id campaign._commit() log_text = 'created %s' % az_cfmap PromotionLog.add(link, log_text) g.log.info(log_text) return az_cfmap
def auth_campaign(link, campaign, user, pay_id=None, freebie=False): """ Authorizes (but doesn't charge) a budget with authorize.net. Args: - link: promoted link - campaign: campaign to be authorized - user: Account obj of the user doing the auth (usually the currently logged in user) - pay_id: customer payment profile id to use for this transaction. (One user can have more than one payment profile if, for instance, they have more than one credit card on file.) Set pay_id to -1 for freebies. Returns: (True, "") if successful or (False, error_msg) if not. """ void_campaign(link, campaign, reason='changed_payment') if freebie: trans_id, reason = authorize.auth_freebie_transaction( campaign.total_budget_dollars, user, link, campaign._id) else: trans_id, reason = authorize.auth_transaction( campaign.total_budget_dollars, user, pay_id, link, campaign._id) if trans_id and not reason: text = ('updated payment and/or budget for campaign %s: ' 'SUCCESS (trans_id: %d, amt: %0.2f)' % (campaign._id, trans_id, campaign.total_budget_dollars)) PromotionLog.add(link, text) if trans_id < 0: PromotionLog.add(link, 'FREEBIE (campaign: %s)' % campaign._id) if trans_id: if is_finished(link): # When a finished promo gets a new paid campaign it doesn't # need to go through approval again and is marked accepted new_status = PROMOTE_STATUS.accepted else: new_status = max(PROMOTE_STATUS.unseen, link.promote_status) else: new_status = max(PROMOTE_STATUS.unpaid, link.promote_status) update_promote_status(link, new_status) if user and (user._id == link.author_id) and trans_id > 0: emailer.promo_total_budget(link, campaign.total_budget_dollars, campaign.start_date) else: text = ( "updated payment and/or budget for campaign %s: FAILED ('%s')" % (campaign._id, reason)) PromotionLog.add(link, text) trans_id = 0 campaign.trans_id = trans_id campaign._commit() return bool(trans_id), reason
def finalize_completed_campaigns(daysago=1): # PromoCampaign.end_date is utc datetime with year, month, day only now = datetime.datetime.now(g.tz) date = now - datetime.timedelta(days=daysago) date = date.replace(hour=0, minute=0, second=0, microsecond=0) q = PromoCampaign._query( PromoCampaign.c.end_date == date, # exclude no transaction and freebies PromoCampaign.c.trans_id > 0, data=True) campaigns = list(q) # check that traffic is up to date earliest_campaign = min(campaigns, key=lambda camp: camp.start_date) start, end = promote.get_total_run(earliest_campaign) missing_traffic = get_missing_traffic(start.replace(tzinfo=None), date.replace(tzinfo=None)) if missing_traffic: raise ValueError("Can't finalize campaigns finished on %s." "Missing traffic from %s" % (date, missing_traffic)) links = Link._byID([camp.link_id for link in links], data=True) for camp in campaigns: if hasattr(camp, 'refund_amount'): continue link = links[camp.link_id] billable_impressions = promote.get_billable_impressions(camp) billable_amount = promote.get_billable_amount(camp, billable_impressions) if billable_amount >= camp.bid: text = ('%s completed with $%s billable (%s impressions @ $%s).' % (camp, billable_amount, billable_impressions, camp.cpm)) PromotionLog.add(link, text) refund_amount = 0. else: refund_amount = camp.bid - billable_amount user = Account._byID(link.author_id, data=True) try: success = authorize.refund_transaction(user, camp.trans_id, camp._id, refund_amount) except authorize.AuthorizeNetException as e: text = ('%s $%s refund failed' % (camp, refund_amount)) PromotionLog.add(link, text) g.log.debug(text + ' (response: %s)' % e) continue text = ('%s completed with $%s billable (%s impressions @ $%s).' ' %s refunded.' % (camp, billable_amount, billable_impressions, camp.cpm, refund_amount)) PromotionLog.add(link, text) camp.refund_amount = refund_amount camp._commit()
def auth_campaign(link, campaign, user, pay_id): """ Authorizes (but doesn't charge) a bid with authorize.net. Args: - link: promoted link - campaign: campaign to be authorized - user: Account obj of the user doing the auth (usually the currently logged in user) - pay_id: customer payment profile id to use for this transaction. (One user can have more than one payment profile if, for instance, they have more than one credit card on file.) Set pay_id to -1 for freebies. Returns: (True, "") if successful or (False, error_msg) if not. """ void_campaign(link, campaign) test = 1 if g.debug else None trans_id, reason = authorize.auth_transaction(campaign.bid, user, pay_id, link, campaign._id, test=test) if trans_id and not reason: text = ('updated payment and/or bid for campaign %s: ' 'SUCCESS (trans_id: %d, amt: %0.2f)' % (campaign._id, trans_id, campaign.bid)) PromotionLog.add(link, text) if trans_id < 0: PromotionLog.add(link, 'FREEBIE (campaign: %s)' % campaign._id) if trans_id: new_status = max(PROMOTE_STATUS.unseen, link.promote_status) else: new_status = max(PROMOTE_STATUS.unpaid, link.promote_status) set_promote_status(link, new_status) # notify of campaign creation # update the query queue if user and (user._id == link.author_id) and trans_id > 0: emailer.promo_bid(link, campaign.bid, campaign.start_date) else: # something bad happend. text = ("updated payment and/or bid for campaign %s: FAILED ('%s')" % (campaign._id, reason)) PromotionLog.add(link, text) trans_id = 0 campaign.trans_id = trans_id campaign._commit() return bool(trans_id), reason
def unapprove_promotion(link): PromotionLog.add(link, 'status update: unapproved') # update the query queue set_promote_status(link, PROMOTE_STATUS.unseen)
def delete_campaign(link, campaign): PromotionWeights.delete_unfinished(link, campaign._id) void_campaign(link, campaign) campaign.delete() PromotionLog.add(link, 'deleted campaign %s' % campaign._id)