Bug 595319, 595320, 595325, devhub payments flow
This commit is contained in:
Родитель
e9b72942b2
Коммит
f41fb7961b
|
@ -149,12 +149,15 @@ class ContribForm(happyforms.ModelForm):
|
||||||
('org', _lazy('An organization of my choice')))
|
('org', _lazy('An organization of my choice')))
|
||||||
|
|
||||||
recipient = forms.ChoiceField(choices=RECIPIENTS,
|
recipient = forms.ChoiceField(choices=RECIPIENTS,
|
||||||
widget=forms.RadioSelect())
|
widget=forms.RadioSelect(attrs={'class': 'recipient'}))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Addon
|
model = Addon
|
||||||
fields = ('paypal_id', 'suggested_amount', 'annoying')
|
fields = ('paypal_id', 'suggested_amount', 'annoying')
|
||||||
widgets = {'annoying': forms.RadioSelect()}
|
widgets = {
|
||||||
|
'annoying': forms.RadioSelect(),
|
||||||
|
'suggested_amount': forms.TextInput(attrs={'class': 'short'}),
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def initial(addon):
|
def initial(addon):
|
||||||
|
@ -166,8 +169,11 @@ class ContribForm(happyforms.ModelForm):
|
||||||
'annoying': addon.annoying or amo.CONTRIB_PASSIVE}
|
'annoying': addon.annoying or amo.CONTRIB_PASSIVE}
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.cleaned_data['recipient'] == 'dev':
|
try:
|
||||||
check_paypal_id(self.cleaned_data['paypal_id'])
|
if not self.errors and self.cleaned_data['recipient'] == 'dev':
|
||||||
|
check_paypal_id(self.cleaned_data['paypal_id'])
|
||||||
|
except forms.ValidationError, e:
|
||||||
|
self.errors['paypal_id'] = self.error_class(e.messages)
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,9 @@
|
||||||
{{ dev_breadcrumbs(addon) }}
|
{{ dev_breadcrumbs(addon) }}
|
||||||
<h2>{{ title }}</h2>
|
<h2>{{ title }}</h2>
|
||||||
</header>
|
</header>
|
||||||
<section class="primary" role="main">
|
<section class="primary payments" role="main" id="edit-addon">
|
||||||
{% if addon.takes_contributions %}
|
{% set contrib = addon.takes_contributions %}
|
||||||
|
{% if contrib %}
|
||||||
<div id="status-bar">
|
<div id="status-bar">
|
||||||
<p>{{ _('You are currently requesting <b>contributions</b> from users')|safe }}</p>
|
<p>{{ _('You are currently requesting <b>contributions</b> from users')|safe }}</p>
|
||||||
<form method="post" action="{{ url('devhub.addons.payments.disable', addon.id) }}">
|
<form method="post" action="{{ url('devhub.addons.payments.disable', addon.id) }}">
|
||||||
|
@ -17,26 +18,67 @@
|
||||||
<button type="submit">{{ _('Disable Contributions') }}</button>
|
<button type="submit">{{ _('Disable Contributions') }}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="intro">
|
||||||
|
<h3>{{ _('Contributions') }}</h3>
|
||||||
|
<p>{{ _('Voluntary contributions provide a way for users to support your add-on financially. With contributions, you can:') }}</p>
|
||||||
|
<ul>
|
||||||
|
<li>{{ _('Ask for donations in most places your add-on appears') }}</li>
|
||||||
|
<li>{{ _('Allow users to make payments with a credit card or PayPal account') }}</li>
|
||||||
|
<li>{{ _('Receive contributions in your PayPal account or send them to an organization of your choice') }}</li>
|
||||||
|
</ul>
|
||||||
|
<div class="button-wrapper">
|
||||||
|
<a href="#setup" id="do-setup" class="button prominent">{{ _('Set Up Contributions') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="intro">
|
|
||||||
<h3>{{ _('Contributions') }}</h3>
|
|
||||||
<p>{{ _('Voluntary contributions provide a way for users to support your add-on financially. With contributions, you can:') }}</p>
|
|
||||||
<ul>
|
|
||||||
<li>{{ _('Ask for donations in most places your add-on appears') }}</li>
|
|
||||||
<li>{{ _('Allow users to make payments with a credit card or PayPal account') }}</li>
|
|
||||||
<li>{{ _('Receive contributions in your PayPal account or send them to a charity') }}</li>
|
|
||||||
</ul>
|
|
||||||
<a href="#setup" class="button">{{ _('Set Up Contributions') }}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="setup">
|
<div id="setup" class="{{ 'hidden' if not contrib }}">
|
||||||
<h3>{{ _('Set up Contributions') }}</h3>
|
<h3>{{ _('Contributions') if contrib else _('Set up Contributions') }}</h3>
|
||||||
<p>{{ _('Fill in the fields below to begin asking for voluntary contributions from users.') }}</p>
|
<p class="{{ 'hidden' if contrib }}">{{ _('Fill in the fields below to begin asking for voluntary contributions from users.') }}</p>
|
||||||
<form method="post" action="">
|
<form method="post" action="">
|
||||||
{{ csrf() }}
|
{{ csrf() }}
|
||||||
<table>{{ contrib_form|safe }}</table>
|
{% set values = contrib_form.data if contrib_form.is_bound else contrib_form.initial %}
|
||||||
<table>{{ charity_form|safe }}</table>
|
<div>
|
||||||
<button type="submit">GO GO GO</button>
|
{{ contrib_form.non_field_errors()|safe }}
|
||||||
|
{{ contrib_form.recipient.errors|safe }}
|
||||||
|
<b>{{ _('Who will receive contributions to this add-on?') }}</b>
|
||||||
|
{{ contrib_form.recipient|safe }}
|
||||||
|
<div id="org-org" class="brform paypal {{ 'hidden' if (values.recipient != 'org') }}">
|
||||||
|
{{ charity_form.non_field_errors()|safe }}
|
||||||
|
{{ charity_form.name.errors|safe }}
|
||||||
|
<label for="id_charity-name">{{ _('What is the name of the organization?') }}</label>
|
||||||
|
{{ charity_form.name|safe }}
|
||||||
|
{{ charity_form.url.errors|safe }}
|
||||||
|
<label for="id_charity-url">{{ _('What is the URL of the organization?') }}</label>
|
||||||
|
{{ charity_form.url|safe }}
|
||||||
|
{{ charity_form.paypal.errors|safe }}
|
||||||
|
<label for="id_charity-paypal">{{ _('What is the PayPal ID of the charity?') }}</label>
|
||||||
|
{{ charity_form.paypal|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="org-dev" class="brform paypal {{ 'hidden' if (values.recipient != 'dev') }}">
|
||||||
|
{{ contrib_form.paypal_id.errors|safe }}
|
||||||
|
<label for="id_paypal_id">{{ _('What is your PayPal ID?') }}</label>
|
||||||
|
<div>{{ contrib_form.paypal_id|safe }} <a class="extra" href="{{ settings.PAYPAL_CGI_URL + '?cmd=_registration-run' }}">{{ _('Sign up for Paypal') }}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="brform">
|
||||||
|
{{ contrib_form.suggested_amount.errors|safe }}
|
||||||
|
<label for="id_suggested_amount">{{ _('What is your suggested contribution?') }}</label>
|
||||||
|
<div class="extra">{{ contrib_form.suggested_amount.help_text }}</div>
|
||||||
|
<div>{{ contrib_form.suggested_amount|safe }} USD</div>
|
||||||
|
</div>
|
||||||
|
<div class="nag">
|
||||||
|
{{ contrib_form.annoying.errors|safe }}
|
||||||
|
<b>{{ _('When should users be asked for contributions?') }}</b>
|
||||||
|
{{ contrib_form.annoying|safe }}
|
||||||
|
</div>
|
||||||
|
<button type="submit">{{ _('Save Changes') if contrib else _('Activate Contributions') }}</button>
|
||||||
|
<span class="{{ 'hidden' if contrib }}">
|
||||||
|
{% trans %}
|
||||||
|
or <a id="setup-cancel" href="#">Cancel</a>
|
||||||
|
{% endtrans %}
|
||||||
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -457,7 +457,7 @@ class TestEditPayments(test_utils.TestCase):
|
||||||
d = dict(recipient='dev', suggested_amount=2,
|
d = dict(recipient='dev', suggested_amount=2,
|
||||||
annoying=amo.CONTRIB_PASSIVE)
|
annoying=amo.CONTRIB_PASSIVE)
|
||||||
r = self.client.post(self.url, d)
|
r = self.client.post(self.url, d)
|
||||||
self.assertFormError(r, 'contrib_form', None,
|
self.assertFormError(r, 'contrib_form', 'paypal_id',
|
||||||
'PayPal id required to accept contributions.')
|
'PayPal id required to accept contributions.')
|
||||||
|
|
||||||
def test_bad_paypal_id_dev(self):
|
def test_bad_paypal_id_dev(self):
|
||||||
|
@ -465,7 +465,7 @@ class TestEditPayments(test_utils.TestCase):
|
||||||
d = dict(recipient='dev', suggested_amount=2, paypal_id='greed@dev',
|
d = dict(recipient='dev', suggested_amount=2, paypal_id='greed@dev',
|
||||||
annoying=amo.CONTRIB_AFTER)
|
annoying=amo.CONTRIB_AFTER)
|
||||||
r = self.client.post(self.url, d)
|
r = self.client.post(self.url, d)
|
||||||
self.assertFormError(r, 'contrib_form', None, 'error')
|
self.assertFormError(r, 'contrib_form', 'paypal_id', 'error')
|
||||||
|
|
||||||
def test_bad_paypal_id_charity(self):
|
def test_bad_paypal_id_charity(self):
|
||||||
self.paypal_mock.return_value = False, 'error'
|
self.paypal_mock.return_value = False, 'error'
|
||||||
|
@ -482,7 +482,7 @@ class TestEditPayments(test_utils.TestCase):
|
||||||
d = dict(recipient='dev', suggested_amount=2, paypal_id='greed@dev',
|
d = dict(recipient='dev', suggested_amount=2, paypal_id='greed@dev',
|
||||||
annoying=amo.CONTRIB_AFTER)
|
annoying=amo.CONTRIB_AFTER)
|
||||||
r = self.client.post(self.url, d)
|
r = self.client.post(self.url, d)
|
||||||
self.assertFormError(r, 'contrib_form', None,
|
self.assertFormError(r, 'contrib_form', 'paypal_id',
|
||||||
'Could not validate PayPal id.')
|
'Could not validate PayPal id.')
|
||||||
|
|
||||||
def test_charity_details_reqd(self):
|
def test_charity_details_reqd(self):
|
||||||
|
|
|
@ -9,7 +9,9 @@ import commonware.log
|
||||||
import jingo
|
import jingo
|
||||||
import path
|
import path
|
||||||
from tower import ugettext_lazy as _lazy
|
from tower import ugettext_lazy as _lazy
|
||||||
|
from tower import ugettext as _
|
||||||
|
|
||||||
|
from amo import messages
|
||||||
import amo.utils
|
import amo.utils
|
||||||
from amo.decorators import json_view, login_required, post_required
|
from amo.decorators import json_view, login_required, post_required
|
||||||
from access import acl
|
from access import acl
|
||||||
|
@ -177,7 +179,10 @@ def payments(request, addon_id, addon):
|
||||||
addon.charity = charity_form.save()
|
addon.charity = charity_form.save()
|
||||||
if valid:
|
if valid:
|
||||||
addon.save()
|
addon.save()
|
||||||
|
messages.success(request, _('Changes successfully saved.'))
|
||||||
return redirect('devhub.addons.payments', addon_id)
|
return redirect('devhub.addons.payments', addon_id)
|
||||||
|
if charity_form.errors or contrib_form.errors:
|
||||||
|
messages.error(request, _('There were errors in your submission.'))
|
||||||
return jingo.render(request, 'devhub/addons/payments.html',
|
return jingo.render(request, 'devhub/addons/payments.html',
|
||||||
dict(addon=addon, charity_form=charity_form,
|
dict(addon=addon, charity_form=charity_form,
|
||||||
contrib_form=contrib_form))
|
contrib_form=contrib_form))
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*clearfix*/
|
||||||
|
.brform:after,
|
||||||
|
#status-bar:after {
|
||||||
|
content: ".";
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
/* @group Edit addon */
|
/* @group Edit addon */
|
||||||
#edit-addon .label {
|
#edit-addon .label {
|
||||||
margin-right:5px;
|
margin-right:5px;
|
||||||
|
@ -397,6 +406,77 @@ input.valid {
|
||||||
}
|
}
|
||||||
/* @end */
|
/* @end */
|
||||||
|
|
||||||
|
/* @group Payments */
|
||||||
|
.payments .intro {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: 1px solid #C9E8F3;
|
||||||
|
padding: 1em;
|
||||||
|
width: 300px;
|
||||||
|
-moz-border-radius: 8px;
|
||||||
|
-webkit-border-radius: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.payments .intro ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
padding: 0 0 .5em 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
border-bottom: 1px dotted #ADD0DC;
|
||||||
|
}
|
||||||
|
.payments .intro ul,
|
||||||
|
.payments .intro p {
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
|
.payments .intro .button-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#edit-addon .brform label,
|
||||||
|
#edit-addon .b {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.brform > label,
|
||||||
|
.brform > input,
|
||||||
|
.brform > div,
|
||||||
|
.brform > ul {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
.payments form > div {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
.paypal {
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
.extra {
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
|
a.extra {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
#status-bar form {
|
||||||
|
float: right;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: .9em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
#status-bar p {
|
||||||
|
float: left;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
#status-bar {
|
||||||
|
padding: .5em 1em;
|
||||||
|
-moz-border-radius: 8px;
|
||||||
|
-webkit-border-radius: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #C9E8F3;
|
||||||
|
}
|
||||||
|
/* @end */
|
||||||
|
|
||||||
/* @group Recent Activity */
|
/* @group Recent Activity */
|
||||||
.secondary-feed {
|
.secondary-feed {
|
||||||
padding: 1em 0 0 1em;
|
padding: 1em 0 0 1em;
|
||||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 150 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 188 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 162 KiB |
|
@ -46,10 +46,87 @@ $("#user-form-template .email-autocomplete")
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
//Ownership
|
||||||
if ($("#author_list").length) {
|
if ($("#author_list").length) {
|
||||||
initAuthorFields();
|
initAuthorFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Payments
|
||||||
|
if ($('.payments').length) {
|
||||||
|
initPayments();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function initPayments() {
|
||||||
|
var previews = [
|
||||||
|
"img/zamboni/contributions/passive.png",
|
||||||
|
"img/zamboni/contributions/after.png",
|
||||||
|
"img/zamboni/contributions/roadblock.png",
|
||||||
|
],
|
||||||
|
media_url = $("body").attr("data-media-url"),
|
||||||
|
to = false,
|
||||||
|
img = $("<img id='contribution-preview'/>");
|
||||||
|
moz = $("input[value=moz]");
|
||||||
|
img.hide().appendTo($("body"));
|
||||||
|
moz.parent().after(
|
||||||
|
$("<a class='extra' target='_blank' href='http://www.mozilla.org/foundation/donate.html'>"+gettext('Learn more')+"</a>"));
|
||||||
|
$(".nag li label").each(function (i,v) {
|
||||||
|
var pl = new Image();
|
||||||
|
pl.src = media_url + previews[i];
|
||||||
|
$(this).after(format(" <a class='extra' href='{0}{1}'>{2}</a>", [media_url, previews[i], gettext('Example')]));
|
||||||
|
});
|
||||||
|
$(".nag").delegate("a.extra", "mouseover", function(e) {
|
||||||
|
var tgt = $(this);
|
||||||
|
img.attr("src", tgt.attr("href")).css({
|
||||||
|
position: 'absolute',
|
||||||
|
'pointer-events': 'none',
|
||||||
|
top: tgt.offset().top-350,
|
||||||
|
left: ($(document).width()-755)/2
|
||||||
|
});
|
||||||
|
clearTimeout(to);
|
||||||
|
to = setTimeout(function() {
|
||||||
|
img.fadeIn(100);
|
||||||
|
}, 300);
|
||||||
|
}).delegate("a.extra", "mouseout", function(e) {
|
||||||
|
clearTimeout(to);
|
||||||
|
img.fadeOut(100);
|
||||||
|
})
|
||||||
|
.delegate("a.extra", "click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
$("#do-setup").click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$("#setup").removeClass("hidden").show();
|
||||||
|
$(".intro").hide();
|
||||||
|
});
|
||||||
|
$("#setup-cancel").click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(".intro").show();
|
||||||
|
$("#setup").hide();
|
||||||
|
});
|
||||||
|
$(".recipient").change(function (e) {
|
||||||
|
var v = $(this).val();
|
||||||
|
$(".paypal").hide(200);
|
||||||
|
$(format("#org-{0}", [v])).removeClass("hidden").show(200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initAuthorFields() {
|
||||||
|
var request = false,
|
||||||
|
timeout = false,
|
||||||
|
manager = $("#id_form-TOTAL_FORMS"),
|
||||||
|
empty_form = template($("#user-form-template").html().replace(/__prefix__/g, "{0}")),
|
||||||
|
author_list = $("#author_list");
|
||||||
|
author_list.sortable({
|
||||||
|
items: ".author",
|
||||||
|
handle: ".handle",
|
||||||
|
containment: author_list,
|
||||||
|
tolerance: "pointer",
|
||||||
|
update: renumberAuthors
|
||||||
|
});
|
||||||
|
addAuthorRow();
|
||||||
|
|
||||||
$("#id_has_eula").change(function (e) {
|
$("#id_has_eula").change(function (e) {
|
||||||
if ($(this).attr("checked")) {
|
if ($(this).attr("checked")) {
|
||||||
$(".eula").show().removeClass("hidden");
|
$(".eula").show().removeClass("hidden");
|
||||||
|
@ -72,22 +149,6 @@ $(document).ready(function() {
|
||||||
$(".license-other").hide();
|
$(".license-other").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
function initAuthorFields() {
|
|
||||||
var request = false,
|
|
||||||
timeout = false,
|
|
||||||
manager = $("#id_form-TOTAL_FORMS"),
|
|
||||||
empty_form = template($("#user-form-template").html().replace(/__prefix__/g, "{0}")),
|
|
||||||
author_list = $("#author_list");
|
|
||||||
author_list.sortable({
|
|
||||||
items: ".author",
|
|
||||||
handle: ".handle",
|
|
||||||
containment: author_list,
|
|
||||||
tolerance: "pointer",
|
|
||||||
update: renumberAuthors
|
|
||||||
});
|
|
||||||
addAuthorRow();
|
|
||||||
|
|
||||||
$(".author .errorlist").each(function() {
|
$(".author .errorlist").each(function() {
|
||||||
$(this).parent()
|
$(this).parent()
|
||||||
|
|
Загрузка…
Ссылка в новой задаче