mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-28 00:07:57 -05:00
Manually refund underdelivered campaigns.
This commit is contained in:
@@ -203,6 +203,8 @@ def make_map():
|
||||
action='edit_promo_campaign')
|
||||
mc('/promoted/pay/:link/:campaign',
|
||||
controller='promote', action='pay')
|
||||
mc('/promoted/refund/:link/:campaign', controller='promote',
|
||||
action='refund')
|
||||
mc('/promoted/graph',
|
||||
controller='promote', action='graph')
|
||||
mc('/promoted/admin/graph', controller='promote', action='admingraph')
|
||||
@@ -330,7 +332,8 @@ def make_map():
|
||||
"freebie|promote_note|update_pay|refund|"
|
||||
"traffic_viewer|rm_traffic_viewer|"
|
||||
"edit_campaign|delete_campaign|meta_promo|"
|
||||
"add_roadblock|rm_roadblock|check_inventory")))
|
||||
"add_roadblock|rm_roadblock|check_inventory|"
|
||||
"refund_campaign")))
|
||||
mc('/api/:action', controller='apiminimal',
|
||||
requirements=dict(action="new_captcha"))
|
||||
mc('/api/:type', controller='api',
|
||||
|
||||
@@ -47,13 +47,14 @@ from r2.lib.pages import (
|
||||
PromoteLinkNew,
|
||||
PromoteReport,
|
||||
Reddit,
|
||||
RefundPage,
|
||||
Roadblocks,
|
||||
UploadedImage,
|
||||
)
|
||||
from r2.lib.pages.trafficpages import TrafficViewerList
|
||||
from r2.lib.pages.things import wrap_links
|
||||
from r2.lib.system_messages import user_added_messages
|
||||
from r2.lib.utils import make_offset_date, to_date
|
||||
from r2.lib.utils import make_offset_date, to_date, to36
|
||||
from r2.lib.validator import (
|
||||
json_validate,
|
||||
nop,
|
||||
@@ -196,6 +197,12 @@ class PromoteController(ListingController):
|
||||
return self.live_by_subreddit(self.sr)
|
||||
elif self.sort == 'live_promos':
|
||||
return queries.get_all_live_links()
|
||||
elif self.sort == 'underdelivered':
|
||||
q = queries.get_underdelivered_campaigns()
|
||||
campaigns = PromoCampaign._by_fullname(list(q), data=True,
|
||||
return_dict=False)
|
||||
link_ids = [camp.link_id for camp in campaigns]
|
||||
return [Link._fullname_from_id36(to36(id)) for id in link_ids]
|
||||
return queries.get_all_promoted_links()
|
||||
else:
|
||||
if self.sort == "future_promos":
|
||||
@@ -315,6 +322,30 @@ class PromoteController(ListingController):
|
||||
if promote.is_promo(thing):
|
||||
promote.reject_promotion(thing, reason=reason)
|
||||
|
||||
@validate(VSponsorAdmin(),
|
||||
link=VLink("link"),
|
||||
campaign=VPromoCampaign("campaign"))
|
||||
def GET_refund(self, link, campaign):
|
||||
if campaign.link_id != link._id:
|
||||
return self.abort404()
|
||||
|
||||
content = RefundPage(link, campaign)
|
||||
return Reddit("refund", content=content, show_sidebar=False).render()
|
||||
|
||||
@validatedForm(VSponsorAdmin(),
|
||||
link=VLink('link'),
|
||||
campaign=VPromoCampaign('campaign'))
|
||||
def POST_refund_campaign(self, form, jquery, link, campaign):
|
||||
billable_impressions = promote.get_billable_impressions(campaign)
|
||||
billable_amount = promote.get_billable_amount(campaign,
|
||||
billable_impressions)
|
||||
refund_amount = campaign.bid - billable_amount
|
||||
if refund_amount > 0:
|
||||
promote.refund_campaign(link, campaign, billable_amount)
|
||||
form.set_html('.status', _('refund succeeded'))
|
||||
else:
|
||||
form.set_html('.status', _('refund not needed'))
|
||||
|
||||
@validatedForm(VSponsor('link_id'),
|
||||
VModhash(),
|
||||
VRatelimit(rate_user=True,
|
||||
|
||||
@@ -745,6 +745,25 @@ def get_all_accepted_links():
|
||||
return _promoted_link_query(None, 'accepted')
|
||||
|
||||
|
||||
@cached_query(UserQueryCache, sort=[desc('_date')])
|
||||
def get_underdelivered_campaigns():
|
||||
return
|
||||
|
||||
|
||||
def set_underdelivered_campaigns(campaigns):
|
||||
campaigns = tup(campaigns)
|
||||
with CachedQueryMutator() as m:
|
||||
q = get_underdelivered_campaigns()
|
||||
m.insert(q, campaigns)
|
||||
|
||||
|
||||
def unset_underdelivered_campaigns(campaigns):
|
||||
campaigns = tup(campaigns)
|
||||
with CachedQueryMutator() as m:
|
||||
q = get_underdelivered_campaigns()
|
||||
m.delete(q, campaigns)
|
||||
|
||||
|
||||
@merged_cached_query
|
||||
def get_promoted_links(user_id):
|
||||
queries = [get_unpaid_links(user_id), get_unapproved_links(user_id),
|
||||
|
||||
@@ -3296,6 +3296,7 @@ class PromotePage(Reddit):
|
||||
buttons.append(NamedButton('admin_graph',
|
||||
dest='/admin/graph'))
|
||||
buttons.append(NavButton('report', 'report'))
|
||||
buttons.append(NavButton('underdelivered', 'underdelivered'))
|
||||
|
||||
menu = NavMenu(buttons, base_path = '/promoted',
|
||||
type='flatlist')
|
||||
@@ -3445,6 +3446,22 @@ class PromoAdminTool(Reddit):
|
||||
return promo_info
|
||||
|
||||
|
||||
class RefundPage(Reddit):
|
||||
def __init__(self, link, campaign):
|
||||
self.link = link
|
||||
self.campaign = campaign
|
||||
self.listing = wrap_links(link, wrapper=promote.sponsor_wrapper,
|
||||
skip=False)
|
||||
billable_impressions = promote.get_billable_impressions(campaign)
|
||||
billable_amount = promote.get_billable_amount(campaign,
|
||||
billable_impressions)
|
||||
refund_amount = campaign.bid - billable_amount
|
||||
self.billable_impressions = billable_impressions
|
||||
self.billable_amount = billable_amount
|
||||
self.refund_amount = refund_amount
|
||||
self.traffic_url = '/traffic/%s/%s' % (link._id36, campaign._id36)
|
||||
Reddit.__init__(self, title="refund", show_sidebar=False)
|
||||
|
||||
|
||||
class Roadblocks(Templated):
|
||||
def __init__(self):
|
||||
|
||||
@@ -41,7 +41,11 @@ from r2.lib import (
|
||||
inventory,
|
||||
hooks,
|
||||
)
|
||||
from r2.lib.db.queries import set_promote_status
|
||||
from r2.lib.db.queries import (
|
||||
set_promote_status,
|
||||
set_underdelivered_campaigns,
|
||||
unset_underdelivered_campaigns,
|
||||
)
|
||||
from r2.lib.memoize import memoize
|
||||
from r2.lib.organic import keep_fresh_links
|
||||
from r2.lib.strings import strings
|
||||
@@ -125,6 +129,12 @@ def view_live_url(l, srname):
|
||||
url += '/r/%s' % srname
|
||||
return 'http://%s/?ad=%s' % (url, l._fullname)
|
||||
|
||||
|
||||
def refund_url(link, campaign):
|
||||
return "%spromoted/refund/%s/%s" % (g.payment_domain, link._id36,
|
||||
campaign._id36)
|
||||
|
||||
|
||||
# booleans
|
||||
|
||||
def is_promo(link):
|
||||
@@ -220,6 +230,11 @@ class RenderableCampaign():
|
||||
status['paid'] = False
|
||||
status['free'] = False
|
||||
|
||||
if complete and user_is_sponsor and not transaction.is_refund():
|
||||
if spent < bid:
|
||||
status['refund'] = True
|
||||
status['refund_url'] = refund_url(link, camp)
|
||||
|
||||
rc = cls(campaign_id36, start_date, end_date, duration, bid, spent,
|
||||
cpm, sr, status)
|
||||
r.append(rc)
|
||||
@@ -776,6 +791,7 @@ def finalize_completed_campaigns(daysago=1):
|
||||
"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'):
|
||||
@@ -793,26 +809,35 @@ def finalize_completed_campaigns(daysago=1):
|
||||
text = '%s completed with $%s billable (pre-CPM).'
|
||||
text %= (camp, billable_amount)
|
||||
PromotionLog.add(link, text)
|
||||
refund_amount = 0.
|
||||
camp.refund_amount = 0.
|
||||
camp._commit()
|
||||
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)
|
||||
underdelivered_campaigns.append(camp)
|
||||
|
||||
camp.refund_amount = refund_amount
|
||||
camp._commit()
|
||||
if underdelivered_campaigns:
|
||||
set_underdelivered_campaigns(underdelivered_campaigns)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
PromoTuple = namedtuple('PromoTuple', ['link', 'weight', 'campaign'])
|
||||
|
||||
@@ -4259,6 +4259,11 @@ ul.tabmenu.formtab {
|
||||
background-image: url(../green-check.png);
|
||||
}
|
||||
|
||||
.existing-campaigns tr.refund {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.existing-campaigns td.bid .info{
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
@@ -272,6 +272,9 @@ function get_flag_class(flags) {
|
||||
if (flags.sponsor) {
|
||||
css_class += " sponsor";
|
||||
}
|
||||
if (flags.refund) {
|
||||
css_class += " refund";
|
||||
}
|
||||
return css_class
|
||||
}
|
||||
|
||||
@@ -295,6 +298,10 @@ $.new_campaign = function(campaign_id36, start_date, end_date, duration,
|
||||
data += ("<input type='hidden' name='view_live_url' value='" +
|
||||
flags.view_live_url + "'/>");
|
||||
}
|
||||
if (flags && flags.refund_url) {
|
||||
data += ("<input type='hidden' name='refund_url' value='" +
|
||||
flags.refund_url + "'/>");
|
||||
}
|
||||
var row = [start_date, end_date, duration, "$" + bid, "$" + spent, targeting, data];
|
||||
$(".existing-campaigns .error").hide();
|
||||
var css_class = get_flag_class(flags);
|
||||
@@ -338,6 +345,7 @@ $.set_up_campaigns = function() {
|
||||
var free = "<button>free</button>";
|
||||
var repay = "<button>change</button>";
|
||||
var view = "<button>view live</button>";
|
||||
var refund = "<button>refund</button>";
|
||||
$(".existing-campaigns tr").each(function() {
|
||||
var tr = $(this);
|
||||
var td = $(this).find("td:last");
|
||||
@@ -348,6 +356,12 @@ $.set_up_campaigns = function() {
|
||||
$(td).append($(view).addClass("view fancybutton")
|
||||
.click(function() { view_campaign(tr) }));
|
||||
}
|
||||
|
||||
if (tr.hasClass('refund')) {
|
||||
$(bid_td).append($(refund).addClass("refund fancybutton")
|
||||
.click(function() { refund_campaign(tr) }));
|
||||
}
|
||||
|
||||
/* once paid, we shouldn't muck around with the campaign */
|
||||
if(!tr.hasClass("complete") && !tr.hasClass("live")) {
|
||||
if (tr.hasClass("sponsor") && !tr.hasClass("free")) {
|
||||
@@ -544,3 +558,7 @@ function pay_campaign(elem) {
|
||||
function view_campaign(elem) {
|
||||
$.redirect($(elem).find('input[name="view_live_url"]').val());
|
||||
}
|
||||
|
||||
function refund_campaign(elem) {
|
||||
$.redirect($(elem).find('input[name="refund_url"]').val());
|
||||
}
|
||||
|
||||
96
r2/r2/templates/refundpage.html
Normal file
96
r2/r2/templates/refundpage.html
Normal file
@@ -0,0 +1,96 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be
|
||||
## consistent with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is reddit Inc.
|
||||
##
|
||||
## All portions of the code written by reddit are Copyright (c) 2006-2013
|
||||
## reddit Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
<%!
|
||||
import simplejson
|
||||
from babel.numbers import format_currency, format_number
|
||||
from r2.lib.utils import to36
|
||||
%>
|
||||
|
||||
<%namespace file="utils.html" import="plain_link"/>
|
||||
|
||||
<div class="refund-promotion">
|
||||
<h1>${_("refund promotion")}</h1>
|
||||
|
||||
${thing.listing}
|
||||
|
||||
<h1>${_("campaign")}</h1>
|
||||
|
||||
<div id="refund-form" class="pretty-form">
|
||||
<table class="content preftable">
|
||||
<tr>
|
||||
<th>${_("id")}</th>
|
||||
<td class="prefright">${thing.campaign._id36}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("dates")}</th>
|
||||
<td class="prefright">
|
||||
${thing.campaign.start_date.strftime("%m/%d/%Y")}
|
||||
-
|
||||
${thing.campaign.end_date.strftime("%m/%d/%Y")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("target")}</th>
|
||||
<td class="prefright">${thing.campaign.sr_name or "Frontpage"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("budget")}</th>
|
||||
<td class="prefright">${format_currency(thing.campaign.bid, 'USD', locale=c.locale)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("cpm")}</th>
|
||||
<td class="prefright">${format_currency(thing.campaign.cpm / 100., 'USD', locale=c.locale)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("impressions purchased")}</th>
|
||||
<td class="prefright">${format_number(thing.campaign.impressions, locale=c.locale)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("impressions received")}</th>
|
||||
<td class="prefright">
|
||||
${format_number(thing.billable_impressions, locale=c.locale)}
|
||||
 
|
||||
(${plain_link(_("detail"), thing.traffic_url)})
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("billable amount")}</th>
|
||||
<td class="prefright">${format_currency(thing.billable_amount, 'USD', locale=c.locale)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${_("refund amount")}</th>
|
||||
<td class="prefright">${format_currency(thing.refund_amount, 'USD', locale=c.locale)}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="hidden" name="link" value="${to36(thing.link._id)}"/>
|
||||
<input type="hidden" name="campaign" value="${to36(thing.campaign._id)}"/>
|
||||
|
||||
<button name="save" class="btn" type="button"
|
||||
onclick="return post_pseudo_form('#refund-form', 'refund_campaign')">
|
||||
${_("issue refund")}
|
||||
</button>
|
||||
<span class="status"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user