add in a wizard from ashort (bug 677535, 677536)
This commit is contained in:
Родитель
2ebc572e50
Коммит
bfee4f93b6
|
@ -963,12 +963,16 @@ class Addon(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
except IndexError:
|
||||
pass
|
||||
|
||||
def has_purchased(self, user):
|
||||
return (self.is_premium() and
|
||||
self.addonpurchase_set.filter(user=user).exists())
|
||||
|
||||
def can_review(self, user):
|
||||
return not self.is_premium() or self.has_purchased(user)
|
||||
@amo.cached_property
|
||||
def upsold(self):
|
||||
"""
|
||||
Return what this is going to upsold from,
|
||||
or None if there isn't one.
|
||||
"""
|
||||
try:
|
||||
return self._upsell_to.all()[0]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def all_dependencies(self):
|
||||
|
|
|
@ -24,7 +24,7 @@ from amo.widgets import EmailWidget
|
|||
from applications.models import Application, AppVersion
|
||||
from files.models import File, FileUpload, Platform
|
||||
from files.utils import parse_addon, VERSION_RE
|
||||
from market.models import Price
|
||||
from market.models import AddonPremium, Price
|
||||
from translations.widgets import TranslationTextarea, TranslationTextInput
|
||||
from translations.fields import TransTextarea, TransField
|
||||
from translations.models import delete_translation
|
||||
|
@ -840,14 +840,18 @@ class PremiumForm(happyforms.Form):
|
|||
text = forms.CharField(widget=forms.Textarea(), required=False)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
self.addon = kw.pop('addon')
|
||||
self.amo_user = kw.pop('amo_user')
|
||||
self.extra = kw.pop('extra')
|
||||
self.addon = self.extra['addon']
|
||||
kw['initial'] = {
|
||||
'paypal_id': self.addon.paypal_id,
|
||||
'price': self.addon.addonpremium.price,
|
||||
'do_upsell': 0,
|
||||
}
|
||||
upsell = self.get_upsell_to()
|
||||
try:
|
||||
kw['initial']['price'] = self.addon.addonpremium.price
|
||||
except AddonPremium.DoesNotExist:
|
||||
pass
|
||||
|
||||
upsell = self.addon.upsold
|
||||
if upsell:
|
||||
kw['initial'].update({
|
||||
'text': upsell.text,
|
||||
|
@ -856,11 +860,18 @@ class PremiumForm(happyforms.Form):
|
|||
})
|
||||
|
||||
super(PremiumForm, self).__init__(*args, **kw)
|
||||
self.fields['free'].queryset = (self.amo_user.addons
|
||||
self.fields['free'].queryset = (self.extra['amo_user'].addons
|
||||
.exclude(pk=self.addon.pk))
|
||||
# For the wizard, we need fields not shown to be optional
|
||||
for field in self.extra.get('not_required', []):
|
||||
self.fields[field].required = False
|
||||
|
||||
def clean_paypal_id(self):
|
||||
token = self.addon.addonpremium.paypal_permissions_token
|
||||
try:
|
||||
token = self.addon.addonpremium.paypal_permissions_token
|
||||
except AddonPremium.DoesNotExist:
|
||||
raise forms.ValidationError(_('No third party token set up.'))
|
||||
|
||||
if not paypal.check_refund_permission(token):
|
||||
raise forms.ValidationError(_('Third party refund not set up.'))
|
||||
return self.cleaned_data['paypal_id']
|
||||
|
@ -875,21 +886,20 @@ class PremiumForm(happyforms.Form):
|
|||
raise forms.ValidationError(_('This field is required.'))
|
||||
return self.cleaned_data['free']
|
||||
|
||||
def get_upsell_to(self):
|
||||
"""
|
||||
If you use addon.upsell you get the free to premium upsell.
|
||||
This is the other, premium to free for the purposes of editing.
|
||||
"""
|
||||
try:
|
||||
return self.addon._upsell_to.all()[0]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
self.addon.update(paypal_id=self.cleaned_data['paypal_id'])
|
||||
self.addon.addonpremium.update(price=self.cleaned_data['price'])
|
||||
if self.cleaned_data['paypal_id']:
|
||||
self.addon.update(paypal_id=self.cleaned_data['paypal_id'])
|
||||
|
||||
upsell = self.get_upsell_to()
|
||||
if self.cleaned_data['price']:
|
||||
try:
|
||||
premium = self.addon.addonpremium
|
||||
except AddonPremium.DoesNotExist:
|
||||
premium = AddonPremium()
|
||||
premium.addon = self.addon
|
||||
premium.price = self.cleaned_data['price']
|
||||
premium.save()
|
||||
|
||||
upsell = self.addon.upsold
|
||||
if (self.cleaned_data['do_upsell'] and
|
||||
self.cleaned_data['text'] and self.cleaned_data['free']):
|
||||
|
||||
|
|
|
@ -357,6 +357,7 @@ class LegacyAddonLog(models.Model):
|
|||
class SubmitStep(models.Model):
|
||||
addon = models.ForeignKey(Addon)
|
||||
step = models.IntegerField()
|
||||
type = models.CharField(max_length=20, default='submit')
|
||||
|
||||
class Meta:
|
||||
db_table = 'submit_step'
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<div id="org-dev" class="brform paypal">
|
||||
<label for="id_paypal_id">{{ _('What is your PayPal ID?') }}</label>
|
||||
<div class="extra">
|
||||
{% trans %}You must have PayPal configured to allow third party refunds
|
||||
permissions from this account.{% endtrans %}
|
||||
{% if not premium.paypal_permissions_token %}
|
||||
<strong>
|
||||
{% trans %}You do not have refund permission setup,
|
||||
<a href="{{ paypal_url }}">click here to set this up on Paypal</a>.
|
||||
{% endtrans %}
|
||||
</strong>
|
||||
{% else %}
|
||||
{% trans %}You have the refund permission setup.{% endtrans %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form.paypal_id.errors }}
|
||||
<div>{{ form.paypal_id }}</div>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<div class="brform">
|
||||
<label for="id_price">{{ form.price.label }}</label>
|
||||
<div class="extra">
|
||||
{% trans doc_url=url('devhub.docs', doc_name='marketplace') %}
|
||||
Select a price tier for your add-on below. Price tiers
|
||||
are based on US Dollars and are fixed for all supported countries. For more
|
||||
information, <a href="{{ doc_url }}">view our pricing matrix</a>.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{{ form.price.errors }}
|
||||
<div>{{ form.price }}</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{% from 'devhub/includes/macros.html' import some_html_tip %}
|
||||
<div class="brform">
|
||||
{{ form.do_upsell.errors }}
|
||||
<div>{{ form.do_upsell }}</div>
|
||||
<div class="indent">
|
||||
{{ form.free.errors }}
|
||||
<div>{{ form.free }}</div>
|
||||
<div class="extra">
|
||||
{% trans %}
|
||||
Pitch your premium add-on, describing the added benefits
|
||||
and functionality over the free version. This will appear
|
||||
on the free add-on's details page.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{{ form.text.errors }}
|
||||
<div>{{ form.text }}</div>
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -15,7 +15,8 @@
|
|||
<p>{% trans %}If this is the free version of your add-on, please create a new UUID
|
||||
for your premium add-on and submit it separately before enrolling in Mozilla Marketplace.
|
||||
{% endtrans %}</p>
|
||||
<button id="marketplace-submit" type="submit">{{ _('Sell this Add-on') }}</button>
|
||||
<a class="button" href="{{ url('devhub.market.1', addon.slug) }}"
|
||||
id="marketplace-submit" type="submit">{{ _('Sell this Add-on') }}</a>
|
||||
{% trans %}
|
||||
or <a id="marketplace-cancel" href="#">Cancel</a>
|
||||
{% endtrans %}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{% extends "devhub/base.html" %}
|
||||
{% from 'devhub/includes/macros.html' import some_html_tip %}
|
||||
|
||||
{% set title = _('Manage Payments') %}
|
||||
{% block title %}{{ dev_page_title(title, addon) }}{% endblock %}
|
||||
|
@ -18,54 +17,9 @@
|
|||
<section class="primary payments devhub-form" role="main">
|
||||
<form method="post" action="{{ url('devhub.addons.payments', addon.slug) }}">
|
||||
{{ csrf() }}
|
||||
<div id="org-dev" class="brform paypal">
|
||||
<label for="id_paypal_id">{{ _('What is your PayPal ID?') }}</label>
|
||||
<div class="extra">
|
||||
{% trans %}You must have PayPal configured to allow third party refunds
|
||||
permissions from this account.{% endtrans %}
|
||||
{% if not premium.paypal_permissions_token %}
|
||||
<strong>
|
||||
{% trans %}You do not have refund permission setup,
|
||||
<a href="{{ paypal_url }}">click here to set this up on Paypal</a>.
|
||||
{% endtrans %}
|
||||
</strong>
|
||||
{% else %}
|
||||
{% trans %}You have the refund permission setup.{% endtrans %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ form.paypal_id.errors }}
|
||||
<div>{{ form.paypal_id }}</div>
|
||||
</div>
|
||||
<div class="brform">
|
||||
<label for="id_price">{{ form.price.label }}</label>
|
||||
<div class="extra">
|
||||
{% trans doc_url=url('devhub.docs', doc_name='marketplace') %}
|
||||
Select a price tier for your add-on below. Price tiers
|
||||
are based on US Dollars and are fixed for all supported countries. For more
|
||||
information, <a href="{{ doc_url }}">view our pricing matrix</a>.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{{ form.price.errors }}
|
||||
<div>{{ form.price }}</div>
|
||||
</div>
|
||||
<div class="brform">
|
||||
{{ form.do_upsell.errors }}
|
||||
<div>{{ form.do_upsell }}</div>
|
||||
<div class="indent">
|
||||
{{ form.free.errors }}
|
||||
<div>{{ form.free }}</div>
|
||||
<div class="extra">
|
||||
{% trans %}
|
||||
Pitch your premium add-on, describing the added benefits
|
||||
and functionality over the free version. This will appear
|
||||
on the free add-on's details page.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
{{ form.text.errors }}
|
||||
<div>{{ form.text }}</div>
|
||||
{{ some_html_tip() }}
|
||||
</div>
|
||||
</div>
|
||||
{% include "devhub/payments/includes/paypal.html" %}
|
||||
{% include "devhub/payments/includes/tier.html" %}
|
||||
{% include "devhub/payments/includes/upsell.html" %}
|
||||
<button type="submit">{{ _('Save Changes') }}</button>
|
||||
</form>
|
||||
</section>
|
||||
|
|
|
@ -858,6 +858,61 @@ class TestMarketplace(amo.tests.TestCase):
|
|||
self.addon = Addon.objects.get(pk=self.addon.pk)
|
||||
eq_(self.addon.addonpremium.paypal_permissions_token, 'FOO')
|
||||
|
||||
@mock.patch('paypal.get_permissions_token', lambda x, y: x.upper())
|
||||
def test_permissions_token_no_premium(self):
|
||||
self.setup_premium()
|
||||
# They could hit this URL before anything else, we need to cope
|
||||
# with AddonPremium not being there.
|
||||
self.addon.addonpremium.delete()
|
||||
url = reverse('devhub.addons.acquire_refund_permission',
|
||||
args=[self.addon.slug])
|
||||
data = {'request_token': 'foo', 'verification_code': 'bar'}
|
||||
self.client.get('%s?%s' % (url, urlencode(data)))
|
||||
self.addon = Addon.objects.get(pk=self.addon.pk)
|
||||
eq_(self.addon.addonpremium.paypal_permissions_token, 'FOO')
|
||||
|
||||
@mock.patch('devhub.forms.PremiumForm.clean_paypal_id')
|
||||
def test_wizard_step_1(self, clean_paypal_id):
|
||||
url = reverse('devhub.market.1', args=[self.addon.slug])
|
||||
data = 'some@paypal.com'
|
||||
clean_paypal_id.return_value = data
|
||||
eq_(self.client.post(url, {'paypal_id': data}).status_code, 302)
|
||||
eq_(Addon.objects.get(pk=self.addon.pk).paypal_id, data)
|
||||
|
||||
def test_wizard_step_2(self):
|
||||
self.price = Price.objects.create(price='0.99')
|
||||
url = reverse('devhub.market.2', args=[self.addon.slug])
|
||||
eq_(self.client.post(url, {'price': self.price.pk}).status_code, 302)
|
||||
eq_(Addon.objects.get(pk=self.addon.pk).premium.price.pk,
|
||||
self.price.pk)
|
||||
|
||||
def test_wizard_step_3(self):
|
||||
url = reverse('devhub.market.3', args=[self.addon.slug])
|
||||
self.other_addon = Addon.objects.create(type=amo.ADDON_EXTENSION)
|
||||
AddonUser.objects.create(addon=self.other_addon,
|
||||
user=self.addon.authors.all()[0])
|
||||
data = {
|
||||
'free': self.other_addon.pk,
|
||||
'do_upsell': 1,
|
||||
'text': 'some upsell'
|
||||
}
|
||||
eq_(self.client.post(url, data).status_code, 302)
|
||||
eq_(Addon.objects.get(pk=self.addon.pk).upsold.free,
|
||||
self.other_addon)
|
||||
|
||||
def test_wizard_step_4_failed(self):
|
||||
url = reverse('devhub.market.4', args=[self.addon.slug])
|
||||
assert not Addon.objects.get(pk=self.addon.pk).is_premium()
|
||||
eq_(self.client.post(url, {}).status_code, 302)
|
||||
assert not Addon.objects.get(pk=self.addon.pk).is_premium()
|
||||
|
||||
def test_wizard_step_4(self):
|
||||
url = reverse('devhub.market.4', args=[self.addon.slug])
|
||||
assert not Addon.objects.get(pk=self.addon.pk).is_premium()
|
||||
self.setup_premium()
|
||||
eq_(self.client.post(url, {}).status_code, 302)
|
||||
assert Addon.objects.get(pk=self.addon.pk).is_premium()
|
||||
|
||||
|
||||
class TestDelete(amo.tests.TestCase):
|
||||
fixtures = ('base/apps', 'base/users', 'base/addon_3615',
|
||||
|
|
|
@ -20,6 +20,13 @@ submit_patterns = patterns('',
|
|||
url('^bump$', views.submit_bump, name='devhub.submit.bump'),
|
||||
)
|
||||
|
||||
marketplace_patterns = patterns('',
|
||||
url('^1$', views.marketplace_paypal, name='devhub.market.1'),
|
||||
url('^2$', views.marketplace_pricing, name='devhub.market.2'),
|
||||
url('^3$', views.marketplace_upsell, name='devhub.market.3'),
|
||||
url('^4$', views.marketplace_confirm, name='devhub.market.4')
|
||||
)
|
||||
|
||||
# These will all start with /addon/<addon_id>/
|
||||
detail_patterns = patterns('',
|
||||
# Redirect to the edit page from the base.
|
||||
|
@ -37,6 +44,7 @@ detail_patterns = patterns('',
|
|||
name='devhub.addons.payments.disable'),
|
||||
url('^payments/permission/refund$', views.acquire_refund_permission,
|
||||
name='devhub.addons.acquire_refund_permission'),
|
||||
url('^payments/', include(marketplace_patterns)),
|
||||
url('^profile$', views.profile, name='devhub.addons.profile'),
|
||||
url('^profile/remove$', views.remove_profile,
|
||||
name='devhub.addons.profile.remove'),
|
||||
|
|
|
@ -38,7 +38,7 @@ from amo.urlresolvers import reverse
|
|||
from access import acl
|
||||
from addons import forms as addon_forms
|
||||
from addons.decorators import addon_view
|
||||
from addons.models import Addon, AddonUser
|
||||
from addons.models import Addon, AddonPremium, AddonUser
|
||||
from addons.views import BaseFilter
|
||||
from devhub.decorators import dev_required
|
||||
from devhub.forms import CheckCompatibilityForm
|
||||
|
@ -47,6 +47,7 @@ from devhub import perf
|
|||
from editors.helpers import get_position
|
||||
from files.models import File, FileUpload, Platform
|
||||
from files.utils import parse_addon
|
||||
from market.models import Price
|
||||
import paypal
|
||||
from translations.models import Translation, delete_translation
|
||||
from users.models import UserProfile
|
||||
|
@ -401,7 +402,8 @@ def payments(request, addon_id, addon):
|
|||
|
||||
def _premium(request, addon_id, addon):
|
||||
premium_form = forms.PremiumForm(request.POST or None,
|
||||
addon=addon, amo_user=request.amo_user)
|
||||
extra={'addon': addon,
|
||||
'amo_user': request.amo_user})
|
||||
if request.method == 'POST' and premium_form.is_valid():
|
||||
premium_form.save()
|
||||
messages.success(request, _('Changes successfully saved.'))
|
||||
|
@ -418,6 +420,8 @@ def _premium(request, addon_id, addon):
|
|||
# Which is a real pain for our dev servers.
|
||||
if 'malformed' in e.message:
|
||||
log.debug('PayPal does not like your server, set to empty.')
|
||||
else:
|
||||
raise
|
||||
|
||||
return jingo.render(request, 'devhub/payments/premium.html',
|
||||
dict(addon=addon, premium=addon.addonpremium,
|
||||
|
@ -473,13 +477,15 @@ def _save_charity(addon, contrib_form, charity_form):
|
|||
def acquire_refund_permission(request, addon_id, addon):
|
||||
"""This is the callback from Paypal."""
|
||||
log.debug('User approved refund for addon: %s' % addon_id)
|
||||
print paypal.get_permissions_token('b','a')
|
||||
print paypal.get_permissions_token
|
||||
token = paypal.get_permissions_token(request.GET['request_token'],
|
||||
request.GET['verification_code'])
|
||||
log.debug('Got refund token for addon: %s' % addon_id)
|
||||
# Sadly this is an update on a POST.
|
||||
addon.addonpremium.update(paypal_permissions_token=token)
|
||||
try:
|
||||
addon.addonpremium.update(paypal_permissions_token=token)
|
||||
except AddonPremium.DoesNotExist:
|
||||
AddonPremium.objects.create(addon=addon,
|
||||
paypal_permissions_token=token)
|
||||
amo.log(amo.LOG.EDIT_PROPERTIES, addon)
|
||||
return redirect('devhub.addons.payments', addon.slug)
|
||||
|
||||
|
@ -1227,7 +1233,7 @@ def submit_step(step):
|
|||
def decorator(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(request, *args, **kw):
|
||||
max_step = 7
|
||||
max_step =7
|
||||
# We only bounce on pages with an addon id.
|
||||
if 'addon' in kw:
|
||||
addon = kw['addon']
|
||||
|
@ -1236,8 +1242,7 @@ def submit_step(step):
|
|||
max_step = on_step[0].step
|
||||
if max_step < step:
|
||||
# The step was too high, so bounce to the saved step.
|
||||
return redirect('devhub.submit.%s' % max_step,
|
||||
addon.slug)
|
||||
return redirect('devhub.submit.7', addon.slug)
|
||||
elif step != max_step:
|
||||
# We couldn't find a step, so we must be done.
|
||||
return redirect('devhub.submit.7', addon.slug)
|
||||
|
@ -1250,6 +1255,67 @@ def submit_step(step):
|
|||
return decorator
|
||||
|
||||
|
||||
@dev_required
|
||||
def marketplace_paypal(request, addon_id, addon):
|
||||
"""
|
||||
Start of the marketplace wizard, none of this means anything until
|
||||
addon-premium is set, so we'll just save as we go along. Further
|
||||
we might have the PayPal permissions bounce happen at any time
|
||||
so we'll need to cope with AddonPremium being incomplete.
|
||||
"""
|
||||
form = forms.PremiumForm(request.POST or None,
|
||||
extra={'addon': addon,
|
||||
'amo_user': request.amo_user,
|
||||
'not_required': ['price']})
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('devhub.market.2', addon.slug)
|
||||
return jingo.render(request, 'devhub/payments/paypal.html',
|
||||
{'form': form, 'addon': addon, 'premium': None})
|
||||
|
||||
|
||||
@dev_required
|
||||
def marketplace_pricing(request, addon_id, addon):
|
||||
form = forms.PremiumForm(request.POST or None,
|
||||
extra={'addon': addon,
|
||||
'amo_user': request.amo_user,
|
||||
'not_required': ['paypal_id']})
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('devhub.market.3', addon.slug)
|
||||
return jingo.render(request, 'devhub/payments/tier.html',
|
||||
{'form': form, 'addon': addon, 'premium': None})
|
||||
|
||||
|
||||
@dev_required
|
||||
def marketplace_upsell(request, addon_id, addon):
|
||||
form = forms.PremiumForm(request.POST or None,
|
||||
extra={'addon': addon,
|
||||
'amo_user': request.amo_user,
|
||||
'not_required': ['price', 'paypal_id']})
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('devhub.market.4', addon.slug)
|
||||
return jingo.render(request, 'devhub/payments/upsell.html',
|
||||
{'form': form, 'addon': addon, 'premium': None})
|
||||
|
||||
|
||||
@dev_required
|
||||
def marketplace_confirm(request, addon_id, addon):
|
||||
if request.method == 'POST':
|
||||
# Minimum required to become premium.
|
||||
if (not addon.paypal_id or addon.addonpremium.price
|
||||
or addon.addonpremium.paypal_permissions_token):
|
||||
messages.error(request, 'Some required details are missing.')
|
||||
return redirect('devhub.market.1', addon.slug)
|
||||
addon.update(premium_type=amo.ADDON_PREMIUM)
|
||||
return redirect('devhub.addons.payments', addon.slug)
|
||||
|
||||
return jingo.render(request, 'devhub/payments/confirm.html',
|
||||
{'addon': addon, 'upsell': addon.upsold,
|
||||
'premium': addon.premium})
|
||||
|
||||
|
||||
@login_required
|
||||
@submit_step(1)
|
||||
def submit(request, step, webapp=False):
|
||||
|
@ -1285,7 +1351,7 @@ def submit_addon(request, step, webapp=False):
|
|||
|
||||
addon = Addon.from_upload(data['upload'], p)
|
||||
AddonUser(addon=addon, user=request.amo_user).save()
|
||||
SubmitStep.objects.create(addon=addon, step=3)
|
||||
SubmitStep.objects.create(addon=addon, step=3, type='submit')
|
||||
return redirect('devhub.submit.3', addon.slug)
|
||||
template = 'upload_webapp.html' if webapp else 'upload.html'
|
||||
return jingo.render(request, 'devhub/addons/submit/%s' % template,
|
||||
|
|
|
@ -97,7 +97,7 @@ def create_addon_purchase(sender, instance, **kw):
|
|||
class AddonPremium(amo.models.ModelBase):
|
||||
"""Additions to the Addon model that only apply to Premium add-ons."""
|
||||
addon = models.OneToOneField('addons.Addon')
|
||||
price = models.ForeignKey(Price)
|
||||
price = models.ForeignKey(Price, blank=True, null=True)
|
||||
paypal_permissions_token = models.CharField(max_length=255, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE addons_premium MODIFY price_id int(11) NULL;
|
Загрузка…
Ссылка в новой задаче