prettify refund page (bug 704961, bug 705871)

This commit is contained in:
Chris Van 2011-11-29 16:52:32 -08:00
Родитель 68d6c10475
Коммит a02cb78d3e
9 изменённых файлов: 134 добавлений и 110 удалений

Просмотреть файл

@ -1,36 +1,49 @@
{% extends "devhub/base.html" %} {% extends 'devhub/base_impala.html' %}
{% block title %}{{ dev_page_title(_('Issue Refund')) }}{% endblock %} {% set title = _('Issue Refund') %}
{% block title %}{{ dev_page_title(title) }}{% endblock %}
{# TODO(apps): Finalize copy. #}
{% block content %} {% block content %}
<header> <header>
<h1>{{ _('Issue Refund') }}</h1> {{ dev_breadcrumbs(addon) }}
<h1>{{ title }}</h1>
</header> </header>
<form method="post" id="issue-refund" class="item" action=""> <form method="post" action="" id="issue-refund" class="primary island full c">
{% if transaction_id %} {% if transaction_id %}
{{ csrf() }} {{ csrf() }}
<p> <p>
{% trans %} {% with user=contribution.user.display_name,
A refund was requested by {{ user }} for {{ addon_name }}. user_url=contribution.user.get_url_path(),
{% endtrans %} addon_url=addon.get_url_path(),
</p><p> addon_name=addon.name %}
{% trans %} A refund was requested by
Price: {{ price }} <a href="{{ user_url }}" target="_blank">{{ user }}</a> for
{% endtrans %} <a href="{{ addon_url }}" target="_blank">{{ addon_name }}</a>.
</p><p> {% endwith %}
{% trans %} </p>
Purchase date: {{ purchase_date }} <p>
{% endtrans %} {% with price=contribution.get_amount_locale() %}
</p><p> <b>Price:</b> {{ price }}
<button type="submit" name="issue"> {% endwith %}
{{ _('Issue Refund') }} </p>
</button> <p>
<button type="submit" name="decline"> {% with purchase_date=contribution.created|datetime %}
{{ _('Decline Refund') }} <b>Purchase date:</b> {{ purchase_date }}
</button> {% endwith %}
<input type="hidden" name="transaction_id" value="{{ transaction_id }}"> </p>
{% else %} <p>
<p>{{ _('No refundable transaction found.') }}</p> <button type="submit" class="good" name="issue">
{% endif %} {{ _('Issue Refund') }}
</button>
<button type="submit" class="bad" name="decline">
{{ _('Decline Refund') }}
</button>
<input type="hidden" name="transaction_id" value="{{ transaction_id }}">
</p>
{% else %}
<p>{{ loc('No refundable transaction found.') }}</p>
{% endif %}
</form> </form>
{% endblock content %} {% endblock %}

Просмотреть файл

@ -1260,60 +1260,71 @@ class TestIssueRefund(amo.tests.TestCase):
self.paykey = u'fake-paykey' self.paykey = u'fake-paykey'
self.client.login(username='del@icio.us', password='password') self.client.login(username='del@icio.us', password='password')
self.user = UserProfile.objects.get(username='clouserw') self.user = UserProfile.objects.get(username='clouserw')
self.url = reverse('devhub.issue_refund', args=[self.addon.slug]) self.url = self.addon.get_dev_url('issue_refund')
def makePurchase(self, uuid='123456', type=amo.CONTRIB_PURCHASE): def make_purchase(self, uuid='123456', type=amo.CONTRIB_PURCHASE):
return Contribution.objects.create(uuid=uuid, addon=self.addon, return Contribution.objects.create(uuid=uuid, addon=self.addon,
transaction_id=self.transaction_id, transaction_id=self.transaction_id,
user=self.user, paykey=self.paykey, user=self.user, paykey=self.paykey,
amount=Decimal('10'), type=type) amount=Decimal('10'), type=type)
def test_request_issue(self): def test_request_issue(self):
c = self.makePurchase() c = self.make_purchase()
r = self.client.get(self.url, r = self.client.get(self.url, {'transaction_id': c.transaction_id})
data={'transaction_id': c.transaction_id})
doc = pq(r.content) doc = pq(r.content)
eq_(doc('#issue-refund button')[0].text.strip(), 'Issue Refund') eq_(doc('#issue-refund button').length, 2)
eq_(doc('#issue-refund button')[1].text.strip(), 'Decline Refund')
eq_(doc('#issue-refund input[name=transaction_id]').val(), eq_(doc('#issue-refund input[name=transaction_id]').val(),
self.transaction_id) self.transaction_id)
def test_nonexistent_txn(self): def test_nonexistent_txn(self):
r = self.client.get(self.url, data={'transaction_id': 'none'}) r = self.client.get(self.url, {'transaction_id': 'none'})
eq_(r.status_code, 404) eq_(r.status_code, 404)
def test_nonexistent_txn_no_really(self): def test_nonexistent_txn_no_really(self):
r = self.client.get(self.url) r = self.client.get(self.url)
eq_(r.status_code, 404) eq_(r.status_code, 404)
@mock.patch('paypal.refund') def _test_issue(self, refund, destination):
def test_issue(self, refund): c = self.make_purchase()
c = self.makePurchase() r = self.client.post(self.url, {'transaction_id': c.transaction_id,
r = self.client.post(self.url, 'issue': '1'})
data={'transaction_id': c.transaction_id, self.assertRedirects(r, reverse(destination), 302)
'issue': '1'})
eq_(r.status_code, 302)
refund.assert_called_with(self.transaction_id, self.paykey) refund.assert_called_with(self.transaction_id, self.paykey)
eq_(len(mail.outbox), 1) eq_(len(mail.outbox), 1)
assert 'approved' in mail.outbox[0].subject assert 'approved' in mail.outbox[0].subject
@mock.patch('paypal.refund') @mock.patch('paypal.refund')
def test_decline(self, refund): def test_addons_issue(self, refund):
c = self.makePurchase() self._test_issue(refund, 'devhub.addons')
r = self.client.post(self.url,
data={'transaction_id': c.transaction_id, @mock.patch('paypal.refund')
'decline': ''}) def test_apps_issue(self, refund):
eq_(r.status_code, 302) self.addon.update(type=amo.ADDON_WEBAPP)
self._test_issue(refund, 'devhub.apps')
def _test_decline(self, refund, destination):
c = self.make_purchase()
r = self.client.post(self.url, {'transaction_id': c.transaction_id,
'decline': ''})
self.assertRedirects(r, reverse(destination), 302)
assert not refund.called assert not refund.called
eq_(len(mail.outbox), 1) eq_(len(mail.outbox), 1)
assert 'declined' in mail.outbox[0].subject assert 'declined' in mail.outbox[0].subject
@mock.patch('paypal.refund')
def test_addons_decline(self, refund):
self._test_decline(refund, 'devhub.addons')
@mock.patch('paypal.refund')
def test_apps_decline(self, refund):
self.addon.update(type=amo.ADDON_WEBAPP)
self._test_decline(refund, 'devhub.apps')
@mock.patch('paypal.refund') @mock.patch('paypal.refund')
def test_non_refundable_txn(self, refund): def test_non_refundable_txn(self, refund):
c = self.makePurchase('56789', amo.CONTRIB_VOLUNTARY) c = self.make_purchase('56789', amo.CONTRIB_VOLUNTARY)
r = self.client.post(self.url, r = self.client.post(self.url, {'transaction_id': c.transaction_id,
data={'transaction_id': c.transaction_id, 'issue': ''})
'issue': ''})
eq_(r.status_code, 404) eq_(r.status_code, 404)
assert not refund.called assert not refund.called

Просмотреть файл

@ -61,6 +61,7 @@ app_detail_patterns = patterns('',
url('^profile$', views.profile, name='devhub.apps.profile'), url('^profile$', views.profile, name='devhub.apps.profile'),
url('^profile/remove$', views.remove_profile, url('^profile/remove$', views.remove_profile,
name='devhub.apps.profile.remove'), name='devhub.apps.profile.remove'),
url('^issue_refund$', views.issue_refund, name='devhub.apps.issue_refund'),
) )
# These will all start with /addon/<addon_id>/ # These will all start with /addon/<addon_id>/
@ -81,7 +82,8 @@ detail_patterns = patterns('',
url('^payments/permission/refund$', views.acquire_refund_permission, url('^payments/permission/refund$', views.acquire_refund_permission,
name='devhub.addons.acquire_refund_permission'), name='devhub.addons.acquire_refund_permission'),
url('^payments/', include(marketplace_patterns('addons'))), url('^payments/', include(marketplace_patterns('addons'))),
url('^issue_refund$', views.issue_refund, name='devhub.issue_refund'), url('^issue_refund$', views.issue_refund,
name='devhub.addons.issue_refund'),
url('^profile$', views.profile, name='devhub.addons.profile'), url('^profile$', views.profile, name='devhub.addons.profile'),
url('^profile/remove$', views.remove_profile, url('^profile/remove$', views.remove_profile,
name='devhub.addons.profile.remove'), name='devhub.addons.profile.remove'),

Просмотреть файл

@ -492,23 +492,19 @@ def issue_refund(request, addon_id, addon, webapp=False):
if 'issue' in request.POST: if 'issue' in request.POST:
paypal.refund(txn_id, contribution.paykey) paypal.refund(txn_id, contribution.paykey)
contribution.mail_approved() contribution.mail_approved()
paypal_log.error('Refund issued for transaction %r' % (txn_id,)) paypal_log.error('Refund issued for transaction %r' % txn_id)
messages.success(request, 'Refund issued.') messages.success(request, 'Refund issued.')
return redirect('devhub.addons')
else: else:
contribution.mail_declined() contribution.mail_declined()
paypal_log.error('Refund declined for transaction %r' % (txn_id,)) paypal_log.error('Refund declined for transaction %r' % txn_id)
messages.success(request, 'Refund declined.') messages.success(request, 'Refund declined.')
return redirect('devhub.addons') return redirect('devhub.%s' % ('apps' if webapp else 'addons'))
else: else:
return jingo.render(request, 'devhub/payments/issue-refund.html', return jingo.render(request, 'devhub/payments/issue-refund.html',
{'refund_issued': False, {'contribution': contribution,
'user': contribution.user.display_name, 'addon': addon,
'addon_name': addon.name,
'webapp': webapp, 'webapp': webapp,
'price': contribution.amount, 'transaction_id': txn_id})
'transaction_id': txn_id,
'purchase_date': contribution.created})
@dev_required @dev_required
@ -1028,7 +1024,6 @@ def addons_section(request, addon_id, addon, section, editable=False,
else: else:
form = False form = False
#import pdb; pdb.set_trace()
data = {'addon': addon, data = {'addon': addon,
'webapp': webapp, 'webapp': webapp,
'form': form, 'form': form,

Просмотреть файл

@ -467,7 +467,6 @@ def profile(request, user_id):
.filter(following__user=user) .filter(following__user=user)
.order_by('-following__created'))[:10] .order_by('-following__created'))[:10]
edit_any_user = acl.action_allowed(request, 'Admin', 'EditAnyUser') edit_any_user = acl.action_allowed(request, 'Admin', 'EditAnyUser')
own_profile = (request.user.is_authenticated() and own_profile = (request.user.is_authenticated() and
request.amo_user.id == user.id) request.amo_user.id == user.id)
@ -760,11 +759,8 @@ def support_mozilla(request, contribution, wizard):
def refund_request(request, contribution, wizard): def refund_request(request, contribution, wizard):
addon = contribution.addon addon = contribution.addon
form = forms.RemoveForm(request.POST or None) form = forms.RemoveForm(request.POST or None)
if request.method == 'POST': if request.method == 'POST' and form.is_valid():
if form.is_valid(): return redirect('users.support', contribution.pk, 'reason')
return redirect(reverse('users.support',
args=[contribution.pk, 'reason']))
return wizard.render(request, wizard.tpl('request.html'), return wizard.render(request, wizard.tpl('request.html'),
{'addon': addon, 'webapp': addon.is_webapp(), {'addon': addon, 'webapp': addon.is_webapp(),
'form': form, 'contribution': contribution}) 'form': form, 'contribution': contribution})
@ -773,35 +769,30 @@ def refund_request(request, contribution, wizard):
def refund_reason(request, contribution, wizard): def refund_reason(request, contribution, wizard):
addon = contribution.addon addon = contribution.addon
if not 'request' in wizard.get_progress(): if not 'request' in wizard.get_progress():
return redirect(reverse('users.support', return redirect('users.support', contribution.pk, 'request')
args=[contribution.pk, 'request']))
form = forms.ContactForm(request.POST or None) form = forms.ContactForm(request.POST or None)
if request.method == 'POST': if request.method == 'POST' and form.is_valid():
if form.is_valid(): # TODO(ashort): Reject refund if purchase was more than 30 minutes ago.
# if under 30 minutes, refund url = absolutify(urlparams(addon.get_dev_url('issue_refund'),
# TODO(ashort): add in the logic for under 30 minutes transaction_id=contribution.transaction_id))
refund_url = absolutify(urlparams( template = jingo.render_to_string(request,
reverse('devhub.issue_refund', args=[addon.slug]), wizard.tpl('emails/refund-request.txt'),
transaction_id=contribution.transaction_id)) context={'addon': addon,
'form': form,
'user': request.amo_user,
'contribution': contribution,
'refund_url': url})
log.info('Refund request sent by user: %s for addon: %s' %
(request.amo_user.pk, addon.pk))
# L10n: %s is the addon name.
send_mail(_(u'New Refund Request for %s' % addon.name),
template, request.amo_user.email,
[smart_str(addon.support_email)])
return redirect(reverse('users.support',
args=[contribution.pk, 'refund-sent']))
template = jingo.render_to_string(request, return wizard.render(request, wizard.tpl('refund.html'), {'form': form})
wizard.tpl('emails/refund-request.txt'),
context={'addon': addon, 'form': form,
'user': request.amo_user,
'contribution': contribution,
'refund_url': refund_url})
log.info('Refund request sent by user: %s for addon: %s' %
(request.amo_user.pk, addon.pk))
# L10n: %s is the addon name.
send_mail(_(u'New Refund Request for %s' % addon.name),
template, request.amo_user.email,
[smart_str(addon.support_email)])
return redirect(reverse('users.support',
args=[contribution.pk, 'refund-sent']))
return wizard.render(request, wizard.tpl('refund.html'),
{'contribut': addon, 'form': form})
class SupportWizard(Wizard): class SupportWizard(Wizard):

Просмотреть файл

@ -1,4 +1,4 @@
@import 'lib'; @import '../impala/lib';
.devhub-form .tip, .devhub-form .tip,
.addon-submission-process .tip, .addon-submission-process .tip,
@ -92,3 +92,7 @@ a.remove:hover {
.html-rtl .undo { .html-rtl .undo {
float: left; float: left;
} }
#issue-refund {
font-size: 14px;
}

Просмотреть файл

@ -15,6 +15,17 @@
.box-shadow(0 1px rgba(0, 0, 0, 0.1), 0 -2px rgba(0, 0, 0, 0.1) inset); .box-shadow(0 1px rgba(0, 0, 0, 0.1), 0 -2px rgba(0, 0, 0, 0.1) inset);
border: 0; border: 0;
} }
button.good, .button.add { // Green
background: #489615;
.gradient-two-color(#84C63C, #489615);
}
button.bad, .button.developer, .button.scary { // Red
background: #bc2b1a;
.gradient-two-color(#f84b4e, #bc2b1a);
}
.button { .button {
display: inline-block; display: inline-block;
&.prominent { &.prominent {
@ -23,9 +34,6 @@
.box-shadow(0 3px rgba(0, 0, 0, 0.1), 0 -4px rgba(0, 0, 0, 0.1) inset); .box-shadow(0 3px rgba(0, 0, 0, 0.1), 0 -4px rgba(0, 0, 0, 0.1) inset);
} }
&.add { // Green &.add { // Green
background: #489615;
.gradient-two-color(#84C63C, #489615);
color: #fff;
span { span {
padding-left: 16px; padding-left: 16px;
background: url(../../img/impala/button-icons.png) no-repeat 0 3px; background: url(../../img/impala/button-icons.png) no-repeat 0 3px;
@ -93,9 +101,6 @@
} }
} }
&.developer, &.scary { // Red &.developer, &.scary { // Red
background: #bc2b1a;
.gradient-two-color(#f84b4e, #bc2b1a);
color: #fff;
span { span {
margin-left: -4px; margin-left: -4px;
padding-left: 24px; padding-left: 24px;
@ -106,7 +111,6 @@
&.watch:not(.watching) { // Orange &.watch:not(.watching) { // Orange
background: #ea0; background: #ea0;
.gradient-two-color(#ea0, darken(#ea0, 10%)); .gradient-two-color(#ea0, darken(#ea0, 10%));
color: #fff;
} }
&.platform { &.platform {
display: none; display: none;

Просмотреть файл

@ -32,6 +32,11 @@ p.req {
margin: 0 0 1em; margin: 0 0 1em;
} }
/* CSRF token */
form div[style]:first-child + p {
margin-top: 0;
}
.optional { .optional {
color: @note-gray; color: @note-gray;
font-size: 11px; font-size: 11px;
@ -380,4 +385,3 @@ button.loading-submit:after {
top: 0; top: 0;
width: 16px; width: 16px;
} }

Просмотреть файл

@ -506,7 +506,7 @@ MINIFY_BUNDLES = {
'css/impala/devhub-popups.less', 'css/impala/devhub-popups.less',
'css/impala/devhub-compat.less', 'css/impala/devhub-compat.less',
'css/impala/formset.less', 'css/impala/formset.less',
'css/impala/devhub-forms.less', 'css/devhub/forms.less',
), ),
'zamboni/devhub_impala': ( 'zamboni/devhub_impala': (
'css/impala/developers.less', 'css/impala/developers.less',
@ -514,7 +514,7 @@ MINIFY_BUNDLES = {
'css/impala/devhub-popups.less', 'css/impala/devhub-popups.less',
'css/impala/devhub-compat.less', 'css/impala/devhub-compat.less',
'css/impala/devhub-dashboard.less', 'css/impala/devhub-dashboard.less',
'css/impala/devhub-forms.less', 'css/devhub/forms.less',
), ),
'zamboni/editors': ( 'zamboni/editors': (
'css/zamboni/editors.css', 'css/zamboni/editors.css',