add/remove multiple payment providers on manage payments page, paypal flow done (bug 776665)

This commit is contained in:
Kevin Ngo 2012-07-30 21:55:34 -07:00 коммит произвёл Chris Van
Родитель 519de78b9e
Коммит d5ff95aef2
31 изменённых файлов: 810 добавлений и 374 удалений

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

@ -1,9 +1,10 @@
import hashlib
import logging
import uuid
from tower import ugettext as _
import paypal
from amo.helpers import loc
log = logging.getLogger('z.paypal')
@ -47,21 +48,21 @@ class Check(object):
"""Check that the paypal id is good."""
test_id = 'id'
if not self.paypal_id:
self.failure(test_id, loc('No PayPal id provided.'))
self.failure(test_id, _('No PayPal ID provided.'))
return
valid, msg = paypal.check_paypal_id(self.paypal_id)
if not valid:
self.failure(test_id, loc('You do not seem to have'
' a PayPal account.'))
self.failure(test_id, _('Please enter a valid email.'))
else:
self.pass_(test_id)
def check_refund(self):
"""Check that we have the refund permission."""
test_id = 'refund'
msg = loc('You have not setup permissions for us to check this '
'paypal account.')
msg = _('You have not setup permissions for us to check this '
'PayPal account.')
if not self.addon:
# If there's no addon there's not even any point checking.
return
@ -82,7 +83,7 @@ class Check(object):
status = paypal.check_permission(token, ['REFUND'])
if not status:
self.state['permissions'] = False
self.failure(test_id, loc('No permission to do refunds.'))
self.failure(test_id, _('No permission to do refunds.'))
else:
self.pass_(test_id)
except paypal.PaypalError:
@ -112,8 +113,8 @@ class Check(object):
'email': self.paypal_id})
log.info('Get paykey passed in %s' % currency)
except paypal.PaypalError:
msg = loc('Failed to make a test transaction '
'in %s.' % (currency))
msg = _('Failed to make a test transaction '
'in %s.' % (currency))
self.failure(test_id, msg)
log.info('Get paykey returned an error'
'in %s' % currency, exc_info=True)

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

@ -113,7 +113,7 @@
}
> ul > li {
display: inline-block;
&:not(:last-child):after {
&:not(:first-child):before {
background-color: @note-gray;
border-radius: 5em;
content: "";

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

@ -38,6 +38,36 @@ button, .button {
}
}
.button, button {
&.paypal,
&.bluevia {
font: bold italic 24px/26px Verdana, @sans-stack;
padding: 22px 40px 21px;
min-width: 175px;
}
&.paypal {
.gradient-two-color(#F8EAC4, #EEC546);
color: rgb(51, 70, 118);
text-shadow: 1px 1px #EDE8BF;
small {
font-size: 0.7em;
}
em {
color: rgb(90, 120, 168);
}
&.disabled em {
color: #919497;
}
}
&.bluevia {
background: #def url(../../img/mkt/bluevia.png) 50% -20% no-repeat;
text-indent: -9999px;
&:hover {
background-color: fade(#def, 70%);
}
}
}
.button {
display: inline-block;
&.add { // Green
@ -82,23 +112,6 @@ button, .button {
&.premium {
.gradient-two-color(#E3C17F, #D6913D);
}
&.paypal {
.gradient-two-color(#F8EAC4, #EEC546);
color: rgb(51, 70, 118);
text-shadow: 1px 1px #EDE8BF;
font-style: italic;
font-family: Verdana, @sans-stack;
background-color: #EEC546;
small {
font-size: 0.7em;
}
em {
color: rgb(90, 120, 168);
}
&.disabled em {
color: #919497;
}
}
&.contribute { // Blue
&.prominent b {
background: url(../../img/impala/button-icons.png) no-repeat;

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

@ -437,6 +437,9 @@ button.loading-submit:after,
a {
margin-left: 4px;
}
button a {
color: @white;
}
}
.html-rtl, .login, .modal {

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

@ -331,7 +331,6 @@
a {
color: @note-gray;
&.more-actions {
display: block;
position: relative;
&:after {
border-color: @note-gray transparent transparent;
@ -362,7 +361,7 @@
}
> ul > li {
display: inline-block;
&:not(:last-child):after {
&:not(:first-child):before {
background-color: @note-gray;
border-radius: 5em;
content: "";

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

@ -4,8 +4,153 @@
text-align: center;
}
.inactive {
color: @light-gray;
cursor: default;
&:hover {
color: @light-gray;
cursor: default;
text-decoration: none;
}
span.req, #payments-payment-account p, .status-fail {
color: @light-gray !important;
}
}
.payment-account-actions {
display: block;
float: right;
font-size: 11px;
margin: -10px;
padding: 10px;
}
.payments {
#premium-type {
float: left;
width: 35%;
}
.divider {
width: 5%;
}
.premium-detail {
border-left: 1px dotted @light-gray;
float: right;
padding-left: 10px;
margin-bottom: 12px;
width: 60%;
}
#payments-price-level {
.extra {
margin: 13px 0 5px;
}
}
#payments-currencies {
ul li label, li {
display: inline;
}
ul li label {
font-weight: normal;
margin-right: 4px;
}
input {
margin-right: -2px;
}
}
#payments-payment-account {
.extra, #payment-label {
display: inline;
}
ul label {
font-weight: normal;
}
p {
color: @red;
font-style: italic;
margin-top: 5px;
}
#paypal-id-verify, .loading-submit {
display: inline;
}
.check-icon {
&:before {
.border-radius(8px);
color: @white;
display: inline-block;
font-weight: normal;
height: 16px;
line-height: 14px;
margin: 0 10px 0 2px;
position: relative;
text-align: center;
width: 16px;
}
}
.check-icon.inactive {
&:before {
background-color: @light-gray !important;
}
}
#check-passed:before {
background-color: @green;
content: "\2714"; /* checkmark */
}
#check-failed:before {
background-color: @red;
content: "\00D7"; /* mult symbol */
font-weight: bold;
}
.item-actions {
margin: 2px 0 5px;
.setup-bounce {
display: none;
}
ul {
font-size: 11px;
line-height: 13px;
}
li:hover {
cursor: pointer;
}
}
#paypal-errors {
display: none;
p {
display: inline;
font-style: normal;
}
li.status-fail {
font-style: italic;
line-height: 1.1;
strong {
color: lighten(#D81916, 5%);
font-weight: normal;
}
}
}
.payment-option {
.transition(~'.3s all');
&.deleting {
background: fade(@red, 40%);
}
&.deleted {
border: 0;
height: 0;
overflow: hidden;
padding: 0;
}
}
}
#payments-upsell {
.note {
margin-top: 5px;
}
#upsell-form {
display: none;
}
}
.listing-footer {
clear: both;
margin-top: 1.5em;
}
.button-wrapper {
@ -38,3 +183,68 @@
}
}
}
/* overlays */
.overlay {
.close {
&:hover {
cursor: pointer;
background-position: -25px 0;
background-color: @red;
}
}
h2 {
text-align: left;
font-size: 18px;
font-weight: 400;
}
.brform, input {
margin-top: 10px;
}
form {
margin: 0;
}
a {
display: block;
margin-top: 10px;
}
.listing-footer {
clear:both;
margin: 13px -10px -10px;
padding-bottom: 46px;
}
}
section.choose-payment-account {
text-align: center;
div {
display: inline-block;
padding: 10px;
button {
margin: 0;
}
}
.or {
color: @note-gray;
font: italic 24px/75px Georgia, serif;
margin: 0 1em;
vertical-align: top;
}
}
section.add-paypal-email {
max-width: 400px;
.email {
width: 250px;
}
input.error {
margin-bottom: 10px;
.box-shadow(0 1px 1px 2px @red inset);
}
.email-error {
color: @red;
display: none;
}
.redirect-info {
margin-top: 10px;
font-style: italic;
}
}

Двоичные данные
media/img/mkt/bluevia.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

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

@ -28,48 +28,183 @@ exports.email_setup = function() {
// This is the setup payments form.
exports.payment_setup = function() {
// Set up account modal.
var newPaymentAccount = function(e) {
var overlay = makeOrGetOverlay('choose-payment-account');
overlay.html($('#choose-payment-account-template').html());
handlePaymentOverlay(overlay);
// PayPal chosen.
$('button.paypal').click(_pd(function(e) {
$('.overlay').remove();
var overlay = makeOrGetOverlay('add-paypal-email');
overlay.html($('#add-paypal-email-template').html());
handlePaymentOverlay(overlay);
// Email entered and submitted.
var emailRe = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var continue_button = $('.continue');
continue_button.click(function(e) {
// Validate email.
var postData = {'email': $('.email').val()};
// Setup the PayPal in the backend.
if (emailRe.test(postData.email)) {
$.post(continue_button.data('paypal-url'), postData, function(data) {
// Request permissions for refunds by redirecting to PayPal.
if (data.valid) {
window.location.replace(data.paypal_url);
}
});
} else {
$('.email').addClass('error');
$('.email-error').show();
}
});
}));
$('button.bluevia').click(_pd(function(e) {
alert('TODO: bluevia onboarding');
}));
};
// Remove PayPal account from app.
var paypalRemove = _pd(function(e) {
var success = $('.success h2');
var $remove = $('.paypal-actions #remove-account');
var resp = $.get($remove.data('remove-url'));
if (!resp.success) {
if (success.length) {
success.text(gettext('There was an error removing the Paypal account.'));
} else {
$('#page').prepend($('<section class="full notification-box">' +
'<div class="success"><h2>' +
gettext('There was an error removing the PayPal account.') +
'</h2></div></section>'));
}
}
var $paypal = $('#paypal');
$paypal.addClass('deleting');
setTimeout(function() {
$paypal.addClass('deleted');
// If already existing Django message, replace message.
if (success.length) {
success.text(gettext('PayPal account successfully removed from app.'));
} else {
$('#page').prepend($('<section class="full notification-box">' +
'<div class="success"><h2>' +
gettext('PayPal account successfully removed from app.') +
'</h2></div></section>'));
}
}, 500);
});
var update_forms = function(value) {
var fields = [
// Free
[[], ['payments-support-type', 'payments-price-level',
'payments-upsell']],
[[], ['premium-detail']],
// Premium
[['payments-support-type', 'payments-price-level',
'payments-upsell'], []],
[['premium-detail'], []],
// Premium with in-app
[['payments-support-type', 'payments-price-level',
'payments-upsell'], []],
[['premium-detail'], []],
// Free with in-app
[['payments-support-type'],
['payments-price-level', 'payments-upsell']],
[['payments-support-type'], ['premium-detail']],
// Premium but other.
[[], ['payments-support-type', 'payments-price-level',
'payments-upsell']],
[[], ['premium-detail']]
];
$.each(fields[value][0], function() { $('#' + this).show(); });
$.each(fields[value][1], function() { $('#' + this).hide(); });
// To disable all elements within premium form.
var elements = ['', 'a', 'textarea', 'select', 'input', 'label', 'span', 'p', 'li'];
function getPremiumElements(id) {
var selector = $(id);
$.each(elements, function(index, element) {
selector = selector.add('#' + id + ' ' + element);
});
return selector;
}
$.each(fields[value][1], function() {
var $inactive = getPremiumElements(this);
$inactive.addClass('inactive').unbind('click')
.attr('disabled', 'disabled')
.attr('href', 'javascript:void(0)');
});
$.each(fields[value][0], function() {
var $active = getPremiumElements(this);
$active.removeClass('inactive').removeAttr('disabled');
// Re-enable links.
$.each($('#' + this + ' a'), function(index, link) {
var $link = $(link);
$link.attr('href', $link.data('url'));
});
$('.payment-account-actions').on('click', _pd(newPaymentAccount));
$('#remove-account').on('click', _pd(paypalRemove));
});
};
// Hide or show upsell form.
var $freeSelect = $('#id_free');
var $freeText = $('#upsell-form');
$freeSelect.change(function() {
if ($freeSelect.val()) {
$freeText.show();
} else {
$freeText.hide();
}
}).trigger('change');
if ($('section.payments input[name=premium_type]').length) {
update_forms($('section.payments input[name=premium_type]:checked').val());
$('section.payments input[name=premium_type]').click(function(e) {
update_forms($(this).val());
});
};
}
function handlePaymentOverlay(overlay) {
overlay.addClass('show');
overlay.on('click', '.close', _pd(function() {
overlay.remove();
}));
}
};
exports.check_with_paypal = function() {
// Looks for errors with PayPal account.
var $paypal_verify = $('#paypal-id-verify'),
target = '.paypal-fail';
if ($paypal_verify.length) {
$.get($paypal_verify.attr('data-url'), function(d) {
$paypal_verify.find('p').eq(0).hide();
target = d.valid ? '.paypal-pass' : '.paypal-fail';
$paypal_verify.find(target).show();
$paypal_verify.find(target).show().css('display', 'inline');
var $paypalErrors = $('#paypal-errors');
if (d.message.length) {
$paypalErrors.show();
$('.item-actions .setup-bounce').css('display', 'inline');
// Load the paypal link into the anchor tag.
var setupBounceLink = $('#setup-bounce-link');
var paypalUrl = setupBounceLink.data('paypal-url');
$.post(paypalUrl, { email: $('#paypal-id').text() }, function(data) {
setupBounceLink.attr('href', data.paypal_url);
});
}
$.each(d.message, function(index, value) {
$paypal_verify.find('ul').append(format('<li class="status-fail"><strong>{0}</strong></li>', value));
})
$paypalErrors.find('ul').append(format('<li class="status-fail"><strong>{0}</strong></li>', value));
});
// Make error messages inactive when initial premium type is
// free.
if ($.inArray($('section.payments input[name=premium_type]:checked').val(), [0, 3, 4])) {
$('.status-fail').addClass('inactive');
}
}
);
}
@ -83,4 +218,3 @@ $(document).ready(function() {
dev_paypal.payment_setup();
dev_paypal.check_with_paypal();
});

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

@ -25,6 +25,7 @@ CSS = {
'css/devreg/buttons.less',
# Popups, Modals, Tooltips.
'css/mkt/overlay.less',
'css/devreg/devhub-popups.less',
'css/mkt/device.less',
'css/devreg/tooltips.less',
@ -167,6 +168,7 @@ JS = {
'js/zamboni/storage.js',
'js/zamboni/tabs.js',
'js/impala/serializers.js',
'js/mkt/utils.js',
# jQuery UI.
'js/lib/jquery-ui/jquery.ui.core.js',

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

@ -13,7 +13,6 @@ import waffle
from quieter_formset.formset import BaseFormSet, BaseModelFormSet
from tower import ugettext as _, ugettext_lazy as _lazy, ungettext as ngettext
import amo
import addons.forms
import paypal
@ -25,8 +24,6 @@ from addons.widgets import CategoriesSelectMultiple
from amo.utils import raise_required, remove_icons
from lib.video import tasks as vtasks
from market.models import AddonPremium, Price, PriceCurrency
from mkt.constants.ratingsbodies import (RATINGS_BY_NAME, ALL_RATINGS,
RATINGS_BODIES)
from translations.fields import TransField
from translations.forms import TranslationFormMixin
from translations.models import Translation
@ -34,9 +31,11 @@ from translations.widgets import TransInput, TransTextarea
import mkt
from mkt.constants import APP_IMAGE_SIZES
from mkt.constants.ratingsbodies import (RATINGS_BY_NAME, ALL_RATINGS,
RATINGS_BODIES)
from mkt.inapp_pay.models import InappConfig
from mkt.reviewers.models import RereviewQueue
from mkt.site.forms import AddonChoiceField, APP_UPSELL_CHOICES
from mkt.site.forms import AddonChoiceField
from mkt.webapps.models import (AddonExcludedRegion, ContentRating, ImageAsset,
Webapp)
@ -483,12 +482,12 @@ class PremiumForm(happyforms.Form):
label=_lazy(u'App Price'),
empty_label=None,
required=False)
do_upsell = forms.TypedChoiceField(coerce=lambda x: bool(int(x)),
choices=APP_UPSELL_CHOICES,
widget=forms.RadioSelect(),
required=False)
free = AddonChoiceField(queryset=Addon.objects.none(), required=False,
empty_label='')
label=_lazy(u'This is a paid upgrade of'),
empty_label=_lazy(u'Not an upgrade'))
currencies = forms.MultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
required=False, label=_lazy(u'Supported Non-USD Currencies'))
def __init__(self, *args, **kw):
self.extra = kw.pop('extra')
@ -496,7 +495,6 @@ class PremiumForm(happyforms.Form):
self.addon = self.extra['addon']
kw['initial'] = {
'premium_type': self.addon.premium_type,
'do_upsell': 0,
}
if self.addon.premium:
kw['initial']['price'] = self.addon.premium.price
@ -505,18 +503,30 @@ class PremiumForm(happyforms.Form):
if upsell:
kw['initial'].update({
'free': upsell.free,
'do_upsell': 1,
})
if waffle.switch_is_active('currencies'):
currencies = self.addon.premium.currencies
kw['initial']['currencies'] = currencies
super(PremiumForm, self).__init__(*args, **kw)
if waffle.switch_is_active('currencies'):
choices = (PriceCurrency.objects.values_list('currency', flat=True)
.distinct())
self.fields['currencies'].choices = [(k, k)
for k in choices if k]
if self.addon.is_webapp():
self.fields['premium_type'].label = _lazy(u'Will your app '
'use payments?')
self.fields['price'].label = _(u'App Price')
self.fields['do_upsell'].choices = APP_UPSELL_CHOICES
self.fields['free'].queryset = (self.extra['amo_user'].addons
.exclude(pk=self.addon.pk)
.filter(premium_type__in=amo.ADDON_FREES,
status__in=amo.VALID_STATUSES,
type=self.addon.type))
.exclude(pk=self.addon.pk)
.filter(premium_type__in=amo.ADDON_FREES,
status__in=amo.VALID_STATUSES,
type=self.addon.type))
if (not self.initial.get('price') and
len(list(self.fields['price'].choices)) > 1):
# Tier 0 (Free) should not be the default selection.
@ -534,9 +544,6 @@ class PremiumForm(happyforms.Form):
return self.cleaned_data['price']
def clean_free(self):
if (self.cleaned_data['do_upsell']
and not self.cleaned_data['free']):
raise_required()
return self.cleaned_data['free']
def save(self):
@ -549,7 +556,7 @@ class PremiumForm(happyforms.Form):
premium.save()
upsell = self.addon.upsold
if self.cleaned_data['do_upsell'] and self.cleaned_data['free']:
if self.cleaned_data['free']:
# Check if this app was already a premium version for another app.
if upsell and upsell.free != self.cleaned_data['free']:
@ -559,7 +566,7 @@ class PremiumForm(happyforms.Form):
upsell = AddonUpsell(premium=self.addon)
upsell.free = self.cleaned_data['free']
upsell.save()
elif not self.cleaned_data['do_upsell'] and upsell:
elif not self.cleaned_data['free'] and upsell:
upsell.delete()
# Check for free -> paid for already public apps.
@ -573,6 +580,11 @@ class PremiumForm(happyforms.Form):
RereviewQueue.flag(self.addon, amo.LOG.REREVIEW_FREE_TO_PAID)
self.addon.premium_type = premium_type
if waffle.switch_is_active('currencies'):
currencies = self.cleaned_data['currencies']
self.addon.premium.update(currencies=currencies)
self.addon.save()
# If they checked later in the wizard and then decided they want
@ -655,20 +667,12 @@ class AppFormBasic(addons.forms.AddonFormBase):
class PaypalSetupForm(happyforms.Form):
business_account = forms.ChoiceField(widget=forms.RadioSelect, choices=[],
label=_lazy(u'Do you already have a PayPal Premier '
'or Business account?'))
email = forms.EmailField(required=False,
label=_lazy(u'PayPal email address'))
def __init__(self, *args, **kw):
super(PaypalSetupForm, self).__init__(*args, **kw)
self.fields['business_account'].choices = (('yes', _lazy(u'Yes')),
('no', _lazy(u'No')))
def clean(self):
data = self.cleaned_data
if data.get('business_account') == 'yes' and not data.get('email'):
if not data.get('email'):
msg = _(u'The PayPal email is required.')
self._errors['email'] = self.error_class([msg])
@ -781,19 +785,6 @@ class AppFormSupport(addons.forms.AddonFormBase):
return super(AppFormSupport, self).save(commit)
class CurrencyForm(happyforms.Form):
currencies = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,
required=False,
label=_lazy(u'Other currencies'))
def __init__(self, *args, **kw):
super(CurrencyForm, self).__init__(*args, **kw)
choices = (PriceCurrency.objects.values_list('currency', flat=True)
.distinct())
self.fields['currencies'].choices = [(k, amo.PAYPAL_CURRENCIES[k])
for k in choices if k]
class AppAppealForm(happyforms.Form):
"""
If a developer's app is rejected he can make changes and request

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

@ -62,10 +62,6 @@
(addon.get_dev_url('refunds'), _('Manage Refunds'))
) %}
{% endif %}
{% if addon.needs_paypal() %}
{% do manage_urls.insert(1,
(addon.get_dev_url('paypal_setup'), _('Manage PayPal'))) %}
{% endif %}
{% if addon.is_webapp() and addon.premium_type in amo.ADDON_INAPPS
and waffle.switch('in-app-payments') %}
{% do manage_urls.insert(1,

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

@ -14,9 +14,6 @@
check_addon_ownership(request, addon, support=True) %}
{% do urls.insert(4, (addon.get_dev_url('refunds'), _('Manage Refunds'))) %}
{% endif %}
{% if addon.needs_paypal() %}
{% do urls.insert(4, (addon.get_dev_url('paypal_setup'), _('Manage PayPal'))) %}
{% endif %}
{% if addon.is_webapp() and addon.premium_type in amo.ADDON_INAPPS
and waffle.switch('in-app-payments') %}
{% do urls.insert(4,
@ -39,7 +36,10 @@
<div class="island" id="edit-addon-nav">
<ul class="refinements">
{% for url, title in urls %}
<li{% if request.path.startswith(url) %} class="selected"{% endif %}>
{# Hook up PayPal urls under payment urls #}
<li{% if request.path.startswith(url)
or (request.path.startswith(addon.get_dev_url('paypal_setup'))
and url == addon.get_dev_url('payments')) %} class="selected"{% endif %}>
<a href="{{ url }}">{{ title }}</a></li>
{% endfor %}
</ul>

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

@ -0,0 +1,21 @@
<script type="text/template" id="choose-payment-account-template">
<section class="choose-payment-account">
<h2>{{ _('Select Payment Provider') }}</h2>
<a class="close">{{ _('close') }}</a>
<div id="choose" class="brform c paypal-inline">
<div>
<form>
<button type="submit" class="bluevia prominent">BlueVia</button>
</form>
<a href="https://bluevia.com/" target="_blank">{{ _('Learn More') }}</a>
</div>
<div class="or">{{ _('or') }}</div>
<div>
<form>
<button type="submit" class="paypal prominent">Pay<em>Pal</em></button>
</form>
<a href="https://paypal.com/" target="_blank">{{ _('Learn More') }}</a>
</div>
</div>
</section>
</script>

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

@ -0,0 +1,23 @@
<script type="text/template" id="add-paypal-email-template">
<section class="add-paypal-email">
<h2>{{ _('Add PayPal Account') }}</h2>
<a class="close">{{ _('close') }}</a>
<div class="brform c paypal-inline">
<label>{{ _('What is the email for your PayPal account?') }}</label>
<input type="text" name="email" class="email">
</div>
<span class="email-error">{{ _('Please enter a valid Paypal email.') }}</span>
<a href="{{ paypal_create_url }}" target="_blank">{{ _('No Premier or Business PayPal account? Sign up.') }}</a>
<p class='redirect-info'>
{{ _('On continue, log in to PayPal to give Marketplace
permissions to process refunds and access your PayPal account
information.') }}
</p>
<div class="listing-footer">
<button type="submit" class="continue prominent"
data-paypal-url="{{ addon.get_dev_url('paypal_setup') }}">
{{ _('Continue') }}
</button>
</div>
</section>
</script>

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

@ -0,0 +1,8 @@
{% from 'developers/includes/macros.html' import required %}
<div class="brform" id="payments-currencies">
<div class="extra">
</div>
<label for="id_price">{{ form.currencies.label }}</label>
{{ form.currencies.errors }}
<div class="indent">{{ form.currencies }}</div>
</div>

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

@ -0,0 +1,62 @@
{% from 'developers/includes/macros.html' import required %}
<div class="brform" id="payments-payment-account">
<label id="payment-label" for="id_payment_account">{{ _('Use Payment Account') }} {{ required() }}</label>
<div class="extra">
<a href="#" class="payment-account-actions" data-action="add">{{ _('Add Account') }}</a>
</div>
{% if addon.paypal_id %}
<div id="paypal" class="indent payment-option">
<label for="paypal-account">PayPal</label>
<span id="paypal-id">{{ addon.paypal_id }}</span>
<div id="paypal-id-verify" data-url="{{ addon.get_dev_url('paypal_setup_check') }}">
<p class="loading-submit"></p>
<div class="paypal-pass js-hidden">
<span id="check-passed" class="check-icon"></span>
</div>
<div class="paypal-fail js-hidden">
<span id="check-failed" class="check-icon"></span>
</div>
<div class="item-actions paypal-actions">
<ul>
<li>
<a id="remove-account"
data-remove-url="{{ addon.get_dev_url('paypal_remove') }}">
{{ _('Remove Account') }}
</a>
</li>
<li>
<a href="{{ addon.get_dev_url('paypal_setup_details')}}"
data-url="{{ addon.get_dev_url('paypal_setup_details') }}">
{{ _('Edit Contact Details') }}
</a>
</li>
<li class="setup-bounce">
<a id="setup-bounce-link"
data-paypal-url="{{ addon.get_dev_url('paypal_setup') }}">
{{ _('Set Up Permissions') }}
</a>
</li>
</ul>
</div>
<div id="paypal-errors">
<p><strong>
{{ _('We found errors on your PayPal account') }}:
</strong></p>
<ul></ul>
</div>
</div>
</div>
<div id="bluevia" class="indent payment-option">
</div>
{% else %}
<p>{{ _('You have not yet setup any payment providers. You will not be able to
receive payments until you do so.') }}</p>
{% endif %}
</div>

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

@ -1,14 +1,15 @@
{% from 'developers/includes/macros.html' import required %}
<div class="brform" id="payments-price-level">
<label for="id_price">{{ form.price.label }} {{ required() }}</label>
<div class="extra">
<p>
{% trans doc_url='https://developer.mozilla.org/en/Apps/Marketplace_Payments#Price_tiers' %}
Select a price tier 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>.
To sell your app in the Marketplace, you'll first need to set its price
tier below. Price tiers are based on US Dollars and are fixed for all supported
currencies. <a href="{{ doc_url }}">Learn more about price tiers</a>.
{% endtrans %}
</p>
</div>
<label for="id_price">{{ form.price.label }} {{ required() }}</label>
{{ form.price.errors }}
<div>{{ form.price }}</div>
</div>

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

@ -1,24 +1,21 @@
{% from 'developers/includes/macros.html' import some_html_tip %}
<div class="brform c" id="payments-upsell">
{% if form.fields['free'].queryset.count() %}
{{ form.do_upsell.errors }}
{{ form.do_upsell }}
<div class="indent">
{{ form.free.errors }}
<p>{{ form.free }}</p>
<p class="extra">
{% trans %}
Pitch your premium app over the free version. This will appear
on the free app's details page.
{% endtrans %}
</p>
</div>
{{ form.free.errors }}
<label>{{ form.free.label }} {{ required() }}</label>
{{ form.free }}
<p class="note">
{% trans %}
Linking this app will promote your premium app next to the free
version.
{% endtrans %}
</p>
{% else %}
<label>{{ _('App upsell') if webapp else _('Add-on upsell') }}</label>
<label>{{ _('App Upsell') }}</label>
<div class="extra">
{% trans %}
If you have a free app, you can pitch your premium app
on that free app. Currently you have no free apps.
If you have a free app, you can link and promote your premium app
next to the free version here.
{% endtrans %}
</div>
{% endif %}

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

@ -4,8 +4,8 @@
{% block content %}
<header>
{{ hub_breadcrumbs(addon, items=[(addon.get_dev_url('paypal_setup'),
_('Manage PayPal')),
{{ hub_breadcrumbs(addon, items=[(addon.get_dev_url('payments'),
_('Manage Payments')),
(None, title)]) }}
<h1>{{ title }}</h1>
</header>
@ -71,10 +71,10 @@
</div>
</fieldset>
<footer class="listing-footer">
<form action="{{ addon.get_dev_url('payments') }}" method="get">
<button type="submit">{{ _('Cancel') }}</button>
</form>
<button type="submit">{{ button }}</button>
{% trans cancel=addon.get_dev_url('paypal_setup') %}
or <a href="{{ cancel }}">Cancel</a>
{% endtrans %}
</footer>
</form>
</section>

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

@ -1,23 +0,0 @@
{% extends 'developers/base_impala.html' %}
{% set title = _('Request Permission') %}}
{% block title %}{{ hub_page_title(title, addon) }}{% endblock %}
{% block content %}
<header>
{{ hub_breadcrumbs(addon, items=[(addon.get_dev_url('paypal_setup'),
_('Manage PayPal')),
(None, title)]) }}
<h1>{{ title }}</h1>
</header>
<section class="primary payments island devhub-form" role="main">
<p>{{ _('In order to process refunds you approve through the Marketplace
as well as automatic refunds, we need you to give permission to us
in PayPal. We also request permission to read your account information
from PayPal.') }}</p>
<footer class="listing-footer">
<a href="{{ paypal_url }}" class="button">{{ _('Set up permissions') }}</a>
</footer>
</section>
{% include 'developers/includes/addons_edit_nav.html' %}
{% endblock %}

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

@ -46,8 +46,7 @@
<form method="post">
{{ csrf() }}
<div class="brform paypal-inline paypal devhub-form">
{{ form_field(paypal_form.business_account) }}
{{ form_field(paypal_form.email) }}
</div>
<footer class="listing-footer">
<button type="submit" name="form" value="paypal">{{ _('Change') }}</button>

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

@ -19,29 +19,35 @@
<section class="primary payments island devhub-form" role="main">
<form method="post" action="{{ addon.get_dev_url('payments') }}">
{{ csrf() }}
<div class="brform" id="payments-premium-type">
<div class="brform" id="premium-type">
<label for="id_premium_type">
{{ form.premium_type.label }} {{ required() }}
</label>
<div class="extra">
{{ _('How your app will displayed on the Marketplace.') }}
</div>
{{ form.premium_type.errors }}
<div class="choice">{{ form.premium_type }}</div>
<p>
{% trans payments_url='https://developer.mozilla.org/en/Apps/Marketplace_Payments',
receipts_url='https://developer.mozilla.org/en/Apps/Validating_a_receipt' %}
Learn about <a href="{{ payments_url }}" target="_blank">different payment types</a>.
Learn about <a href="{{ receipts_url }}" target="_blank">validating purchase receipts in your app</a>.
Learn about <a href="{{ receipts_url }}" target="_blank">validating purchase receipts</a>.
{% endtrans %}
</p>
</div>
{% include 'developers/payments/includes/tier.html' %}
{% include 'developers/payments/includes/upsell.html' %}
<div class="divider"></div>
<div id="premium-detail" class="premium-detail">
{% include 'developers/payments/includes/tier.html' %}
{% if waffle.switch('currencies') %}
{% include 'developers/payments/includes/currencies.html' %}
{% endif %}
{% include 'developers/payments/includes/payment_account.html' %}
{% include 'developers/payments/includes/upsell.html' %}
</div>
<footer class="listing-footer">
<button type="submit">{{ _('Save Changes') }}</button>
</footer>
</form>
</section>
{% include 'developers/includes/addons_edit_nav.html' %}
{% include 'developers/payments/includes/add_payment_account_choose.html' %}
{% include 'developers/payments/includes/add_payment_account_paypal_email.html' %}
{% endblock %}

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

@ -72,24 +72,16 @@ class TestPreviewForm(amo.tests.TestCase):
class TestPaypalSetupForm(amo.tests.TestCase):
def test_email_not_required(self):
data = {'business_account': 'no',
'email': ''}
assert forms.PaypalSetupForm(data=data).is_valid()
def test_email_required(self):
data = {'business_account': 'yes',
'email': ''}
data = {'email': ''}
assert not forms.PaypalSetupForm(data=data).is_valid()
def test_email_gotten(self):
data = {'business_account': 'yes',
'email': 'foo@bar.com'}
data = {'email': 'foo@bar.com'}
assert forms.PaypalSetupForm(data=data).is_valid()
def test_email_malformed(self):
data = {'business_account': 'yes',
'email': 'foo'}
data = {'email': 'foo'}
assert not forms.PaypalSetupForm(data=data).is_valid()
@ -233,7 +225,6 @@ class TestRegionForm(amo.tests.WebappTestCase):
eq_(form.initial['regions'], regions)
eq_(form.initial['other_regions'], False)
def test_worldwide_only(self):
form = forms.RegionForm(data={'other_regions': 'on'}, **self.kwargs)
assert form.is_valid()

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

@ -200,7 +200,6 @@ class TestAppDashboard(AppHubTest):
expected = [
('Manage Status', app.get_dev_url('versions')),
('Manage In-App Payments', app.get_dev_url('in_app_config')),
('Manage PayPal', app.get_dev_url('paypal_setup')),
('Manage Refunds', app.get_dev_url('refunds')),
]
eq_(doc('.status-link').length, 0)
@ -416,6 +415,9 @@ class TestPaymentsProfile(amo.tests.TestCase):
class MarketplaceMixin(object):
def setUp(self):
waffle.models.Switch.objects.get_or_create(name='currencies',
active=True)
self.addon = Addon.objects.get(id=337141)
self.addon.update(status=amo.STATUS_NOMINATED,
highest_status=amo.STATUS_NOMINATED)
@ -445,6 +447,10 @@ class MarketplaceMixin(object):
self.addon.update(premium_type=amo.ADDON_PREMIUM,
paypal_id='a@a.com')
session = self.client.session
session['unconfirmed_paypal_id'] = self.addon.paypal_id
session.save()
# Mock out verifying the paypal id has refund permissions with paypal and
# that the account exists on paypal.
@ -452,15 +458,13 @@ class MarketplaceMixin(object):
@mock.patch('mkt.developers.forms.PremiumForm.clean',
new=lambda x: x.cleaned_data)
class TestMarketplace(MarketplaceMixin, amo.tests.TestCase):
fixtures = ['base/users', 'webapps/337141-steamcube']
fixtures = ['base/users', 'webapps/337141-steamcube', 'market/prices']
def get_data(self, **kw):
data = {
'price': self.price.pk,
'free': self.other_addon.pk,
'do_upsell': 1,
'premium_type': amo.ADDON_PREMIUM,
'support_email': 'c@c.com',
}
data.update(kw)
return data
@ -473,15 +477,29 @@ class TestMarketplace(MarketplaceMixin, amo.tests.TestCase):
def test_set(self):
self.setup_premium()
res = self.client.post(self.url, data={
'support_email': 'c@c.com',
'price': self.price_two.pk,
'premium_type': amo.ADDON_PREMIUM
})
res = self.client.post(self.url, data=self.get_data(price=self.
price_two.pk))
eq_(res.status_code, 302)
self.addon = Addon.objects.get(pk=self.addon.pk)
eq_(self.addon.addonpremium.price, self.price_two)
def test_set_currency(self):
self.setup_premium()
res = self.client.post(self.url,
data=self.get_data(currencies=['EUR', 'BRL']))
eq_(res.status_code, 302)
self.addon = Addon.objects.get(pk=self.addon.pk)
eq_(self.addon.premium.currencies, ['EUR', 'BRL'])
def test_set_currency_fail(self):
self.setup_premium()
res = self.client.post(self.url,
data=self.get_data(currencies=['EUR', 'LOL']))
eq_(res.status_code, 200)
self.assertFormError(res, 'form', 'currencies',
[u'Select a valid choice. '
'LOL is not one of the available choices.'])
def test_set_upsell(self):
self.setup_premium()
res = self.client.post(self.url, data=self.get_data())
@ -513,7 +531,7 @@ class TestMarketplace(MarketplaceMixin, amo.tests.TestCase):
upsell = AddonUpsell.objects.create(free=self.other_addon,
premium=self.addon)
eq_(self.addon._upsell_to.all()[0], upsell)
self.client.post(self.url, data=self.get_data(do_upsell=0))
self.client.post(self.url, data=self.get_data(free=''))
eq_(len(self.addon._upsell_to.all()), 0)
def test_replace_upsell(self):
@ -542,23 +560,26 @@ class TestMarketplace(MarketplaceMixin, amo.tests.TestCase):
@mock.patch('paypal.get_permissions_token', lambda x, y: x.upper())
@mock.patch('paypal.get_personal_data', lambda x: {'email': 'a@a.com'})
def test_permissions_token(self):
self.setup_premium()
eq_(self.addon.premium.paypal_permissions_token, '')
url = self.addon.get_dev_url('acquire_refund_permission')
self.client.get(urlparams(url, request_token='foo',
verification_code='bar'))
self.addon = Addon.objects.get(pk=self.addon.pk)
eq_(self.addon.premium.paypal_permissions_token, 'FOO')
eq_(self.addon.premium.paypal_permissions_token.lower(), 'foo')
@mock.patch('paypal.get_permissions_token', lambda x, y: x.upper())
@mock.patch('paypal.get_personal_data', lambda x: {})
def test_permissions_token_different_email(self):
# With different email, the new token overwrites the old one, and the
# paypal_id stays the same.
self.setup_premium()
url = self.addon.get_dev_url('acquire_refund_permission')
self.client.get(urlparams(url, request_token='foo',
verification_code='bar'))
self.addon = Addon.objects.get(pk=self.addon.pk)
eq_(self.addon.premium.paypal_permissions_token, '')
eq_(self.addon.premium.paypal_permissions_token.lower(), 'foo')
@mock.patch('mkt.developers.views.client')
def test_permissions_token_solitude(self, client):
@ -580,7 +601,7 @@ class TestMarketplace(MarketplaceMixin, amo.tests.TestCase):
res = self.client.get(urlparams(url, request_token='foo',
verification_code='bar'))
self.assertRedirects(res,
self.addon.get_dev_url('paypal_setup_bounce'))
self.addon.get_dev_url('payments'))
class TestIssueRefund(amo.tests.TestCase):

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

@ -241,7 +241,7 @@ class TestDeveloperRoleAccess(amo.tests.TestCase):
eq_(res.status_code, 403)
def test_urls(self):
urls = ['owner', 'payments', 'paypal_setup']
urls = ['owner', 'payments']
for url in urls:
self._check_it(self.webapp.get_dev_url(url))

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

@ -1,3 +1,5 @@
import json
import mock
from nose.tools import eq_
from pyquery import PyQuery as pq
@ -105,11 +107,6 @@ class TestPaypal(amo.tests.TestCase):
def get_webapp(self):
return Addon.objects.get(pk=337141)
def test_not_premium(self):
self.webapp.update(premium_type=amo.ADDON_FREE)
res = self.client.get(self.url)
eq_(res.status_code, 302)
def test_partial_submit(self):
from mkt.submit.models import AppSubmissionChecklist
AppSubmissionChecklist.objects.create(addon=self.webapp)
@ -117,53 +114,21 @@ class TestPaypal(amo.tests.TestCase):
res = self.client.get(self.url, follow=True)
self.assertRedirects(res, reverse('submit.app.terms'))
@mock.patch('mkt.developers.views.waffle.switch_is_active')
def test_currencies_fails(self, switch_is_active):
switch_is_active.return_value = True
def test_paypal_setup_json(self):
self.webapp.update(premium_type=amo.ADDON_PREMIUM)
res = self.client.post(self.url, {'currencies': ['GBP', 'EUR'],
'form': 'currency'})
eq_(res.status_code, 200)
self.assertFormError(res, 'currency_form', 'currencies',
[u'Select a valid choice. '
'GBP is not one of the available choices.'])
@mock.patch('mkt.developers.views.waffle.switch_is_active')
def test_currencies_passes(self, switch_is_active):
switch_is_active.return_value = True
self.webapp.update(premium_type=amo.ADDON_PREMIUM)
res = self.client.post(self.url, {'currencies': ['EUR', 'BRL'],
'form': 'currency'})
eq_(res.status_code, 302)
eq_(self.webapp.premium.currencies, ['EUR', 'BRL'])
def test_later(self):
self.webapp.update(premium_type=amo.ADDON_PREMIUM)
res = self.client.post(self.url, {'email': 'a@a.com',
'business_account': 'yes',
'form': 'paypal'})
self.assertRedirects(res,
self.webapp.get_dev_url('paypal_setup_bounce'))
@mock.patch('mkt.developers.views.client')
@mock.patch('mkt.developers.views.waffle.flag_is_active')
def test_later_solitude(self, flag_is_active, client):
flag_is_active.return_value = True
self.webapp.update(premium_type=amo.ADDON_PREMIUM)
self.client.post(self.url, {'email': 'a@a.com', 'form': 'paypal',
'business_account': 'yes'})
eq_(client.create_seller_paypal.call_args[0][0], self.webapp)
eq_(client.patch_seller_paypal.call_args[1]['data']['paypal_id'],
'a@a.com')
res = json.loads(self.client.post(self.url, {'email':
'a@a.com'}).content)
eq_(res['valid'], True)
eq_('paypal_url' in res, True)
eq_(len(res['message']), 0)
@mock.patch('mkt.developers.views.client')
def test_bounce_solitude(self, client):
self.create_flag(name='solitude-payments')
url = 'http://foo.com'
client.post_permission_url.return_value = {'token': url}
self.webapp.update(premium_type=amo.ADDON_PREMIUM, paypal_id='a@.com')
res = self.client.post(self.webapp.get_dev_url('paypal_setup_bounce'))
eq_(pq(res.content)('section.primary a.button').attr('href'), url)
url = self.webapp.get_dev_url('payments')
eq_(pq(res.content)('section.primary form')[1].action, url)
class TestPaypalResponse(amo.tests.TestCase):
@ -175,6 +140,10 @@ class TestPaypalResponse(amo.tests.TestCase):
self.webapp.update(status=amo.STATUS_NULL)
self.client.login(username='admin@mozilla.com', password='password')
session = self.client.session
session['unconfirmed_paypal_id'] = 'bob@dog.com'
session.save()
def get_webapp(self):
return Addon.objects.get(pk=337141)

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

@ -13,16 +13,19 @@ def paypal_patterns(prefix):
return patterns('',
url('^$', views.paypal_setup,
name='mkt.developers.%s.paypal_setup' % prefix),
url('^bounce$', views.paypal_setup_confirm,
name='mkt.developers.%s.paypal_setup_bounce' % prefix,
kwargs={'source': 'paypal'}),
url('^confirm$', views.paypal_setup_confirm,
name='mkt.developers.%s.paypal_setup_confirm' % prefix,
kwargs={'source': 'paypal'}),
url('^details$', views.paypal_setup_confirm,
name='mkt.developers.%s.paypal_setup_details' % prefix,
kwargs={'source': 'developers'}),
url('^bounce$', views.paypal_setup_bounce,
name='mkt.developers.%s.paypal_setup_bounce' % prefix),
url('^check$', views.paypal_setup_check,
name='mkt.developers.%s.paypal_setup_check' % prefix),
url('^remove$', views.paypal_remove,
name='mkt.developers.%s.paypal_remove' % prefix),
)

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

@ -52,7 +52,7 @@ from versions.models import Version
from mkt.constants import APP_IMAGE_SIZES
from mkt.developers.decorators import dev_required
from mkt.developers.forms import (AppFormBasic, AppFormDetails, AppFormMedia,
AppFormSupport, CategoryForm, CurrencyForm,
AppFormSupport, CategoryForm,
ImageAssetFormSet, InappConfigForm,
PaypalSetupForm, PreviewFormSet, RegionForm,
trap_duplicate)
@ -281,71 +281,29 @@ def payments(request, addon_id, addon, webapp=False):
return _premium(request, addon_id, addon, webapp)
@dev_required(owner_for_post=True, webapp=True)
def paypal_setup(request, addon_id, addon, webapp):
if addon.premium_type == amo.ADDON_FREE:
messages.error(request, 'Your app does not use payments.')
return redirect(addon.get_dev_url('payments'))
paypal_form = PaypalSetupForm(request.POST or None)
currency_form = CurrencyForm(request.POST or None,
initial={'currencies': addon.premium.currencies
if addon.premium else {}})
context = {'addon': addon, 'paypal_form': paypal_form,
'currency_form': currency_form}
if request.POST.get('form') == 'paypal' and paypal_form.is_valid():
existing = paypal_form.cleaned_data['business_account']
if existing != 'yes':
# Go create an account.
# TODO: this will either become the API or something some better
# URL for the future.
return redirect(settings.PAYPAL_CGI_URL)
else:
# Go setup your details on paypal.
# TODO(solitude): we will remove this.
addon.update(paypal_id=paypal_form.cleaned_data['email'])
if waffle.flag_is_active(request, 'solitude-payments'):
obj = client.create_seller_paypal(addon)
client.patch_seller_paypal(pk=obj['resource_pk'],
data={'paypal_id': paypal_form.cleaned_data['email']})
if addon.premium and addon.premium.paypal_permissions_token:
addon.premium.update(paypal_permissions_token='')
return redirect(addon.get_dev_url('paypal_setup_bounce'))
if (waffle.switch_is_active('currencies') and
request.POST.get('form') == 'currency' and currency_form.is_valid()):
currencies = currency_form.cleaned_data['currencies']
addon.premium.update(currencies=currencies)
messages.success(request, _('Currencies updated.'))
return redirect(addon.get_dev_url('paypal_setup'))
return jingo.render(request, 'developers/payments/paypal-setup.html',
context)
@json_view
@dev_required(owner_for_post=True, webapp=True)
def paypal_setup_check(request, addon_id, addon, webapp):
if waffle.flag_is_active(request, 'solitude-payments'):
data = client.post_account_check(data={'seller': addon})
return {'valid': data['passed'], 'message': data['errors']}
else:
if not addon.paypal_id:
return {'valid': False, 'message': ['No PayPal email.']}
def paypal_setup(request, addon_id, addon, webapp):
paypal_form = PaypalSetupForm(request.POST or None)
check = Check(addon=addon)
check.all()
return {'valid': check.passed, 'message': check.errors}
if paypal_form.is_valid():
# Don't save the paypal_id into the addon until we set up permissions
# and confirm it as good.
request.session['unconfirmed_paypal_id'] = (paypal_form
.cleaned_data['email'])
# Go setup your details on paypal.
paypal_url = get_paypal_bounce_url(request, str(addon_id), addon,
webapp, json_view=True)
return {'valid': True, 'message': [], 'paypal_url': paypal_url}
return {'valid': False, 'message': [_('Form not valid')]}
@dev_required(owner_for_post=True, webapp=True)
def paypal_setup_bounce(request, addon_id, addon, webapp):
if not addon.paypal_id:
messages.error(request, 'We need a PayPal email before continuing.')
def get_paypal_bounce_url(request, addon_id, addon, webapp, json_view=False):
if not addon.paypal_id and not json_view:
messages.error(request, _('We need a PayPal email before continuing.'))
return redirect(addon.get_dev_url('paypal_setup'))
dest = 'developers'
@ -359,57 +317,7 @@ def paypal_setup_bounce(request, addon_id, addon, webapp):
# TODO(solitude): remove this.
else:
paypal_url = paypal.get_permission_url(addon, dest, perms)
return jingo.render(request,
'developers/payments/paypal-details-request.html',
{'paypal_url': paypal_url, 'addon': addon})
@dev_required(owner_for_post=True, webapp=True)
def paypal_setup_confirm(request, addon_id, addon, webapp, source='paypal'):
# If you bounce through paypal as you do permissions changes set the
# source to paypal.
if source == 'paypal':
msg = _('PayPal set up complete.')
title = _('Confirm Details')
button = _('Continue')
# If you just hit this page from the Manage Paypal, show some less
# wizardy stuff.
else:
msg = _('Changes saved.')
title = _('Contact Details')
button = _('Save')
data = {}
if waffle.flag_is_active(request, 'solitude-payments'):
data = client.get_seller_paypal_if_exists(addon) or {}
# TODO(solitude): remove this bit.
# If it's not in solitude, use the local version
adp, created = (AddonPaymentData.objects
.safer_get_or_create(addon=addon))
if not data:
data = model_to_dict(adp)
form = forms.PaypalPaymentData(request.POST or data)
if request.method == 'POST' and form.is_valid():
if waffle.flag_is_active(request, 'solitude-payments'):
# TODO(solitude): when the migration of data is completed, we
# will be able to remove this.
pk = client.create_seller_for_pay(addon)
client.patch_seller_paypal(pk=pk, data=form.cleaned_data)
# TODO(solitude): remove this.
adp.update(**form.cleaned_data)
messages.success(request, msg)
if source == 'paypal' and addon.is_incomplete() and addon.paypal_id:
addon.mark_done()
return redirect(addon.get_dev_url('paypal_setup'))
return jingo.render(request,
'developers/payments/paypal-details-confirm.html',
{'addon': addon, 'button': button, 'form': form,
'title': title})
return paypal_url
@write
@ -420,12 +328,13 @@ def acquire_refund_permission(request, addon_id, addon, webapp=False):
# Set up our redirects.
if request.GET.get('dest', '') == 'submission':
on_good = reverse('submit.app.payments.confirm', args=[addon.app_slug])
on_error = reverse('submit.app.payments.paypal', args=[addon.app_slug])
on_error = reverse('submit.app.payments',
args=[addon.app_slug])
show_good_msgs = False
else:
# The management pages are the default.
on_good = addon.get_dev_url('paypal_setup_confirm')
on_error = addon.get_dev_url('paypal_setup_bounce')
on_error = addon.get_dev_url('payments')
show_good_msgs = True
if 'request_token' not in request.GET:
@ -455,18 +364,6 @@ def acquire_refund_permission(request, addon_id, addon, webapp=False):
request.GET['verification_code'])
data = paypal.get_personal_data(token)
# TODO(solitude): remove this.
email = data.get('email')
# If the email from paypal is different, something has gone wrong.
if email != addon.paypal_id:
paypal_log.debug('Addon paypal_id and personal data differ: '
'%s vs %s' % (email, addon.paypal_id))
messages.warning(request, _('The email returned by Paypal, '
'did not match the PayPal email you '
'entered. Please login using %s.')
% email)
return redirect(on_error)
# TODO(solitude): remove this. Sadly because the permissions tokens
# are never being traversed back we have a disconnect between what is
# happening in solitude and here and this will not easily survive flipping
@ -478,6 +375,22 @@ def acquire_refund_permission(request, addon_id, addon, webapp=False):
.safer_get_or_create(addon=addon))
addonpremium.update(paypal_permissions_token=token)
# TODO(solitude): remove this.
# Do this after setting permissions token since the previous permissions
# token becomes invalid after making a call to get_permissions_token.
email = data.get('email')
paypal_id = request.session['unconfirmed_paypal_id']
# If the email from paypal is different, something has gone wrong.
if email != paypal_id:
paypal_log.debug('Addon paypal_id and personal data differ: '
'%s vs %s' %
(email, paypal_id))
messages.warning(request, _('The email returned by PayPal '
'did not match the PayPal email you '
'entered. Please log in using %s.')
% paypal_id)
return redirect(on_error)
# Finally update the data returned from PayPal for this addon.
paypal_log.debug('Updating personal data for: %s' % addon_id)
# TODO(solitude): delete this, as the data was pulled through solitude
@ -499,6 +412,95 @@ def acquire_refund_permission(request, addon_id, addon, webapp=False):
# End of new paypal stuff.
@dev_required(owner_for_post=True, webapp=True)
def paypal_setup_confirm(request, addon_id, addon, webapp, source='paypal'):
# If you bounce through paypal as you do permissions changes set the
# source to paypal.
if source == 'paypal':
msg = _('PayPal setup complete.')
title = _('Confirm Details')
button = _('Continue')
# If you just hit this page from the Manage Paypal, show some less
# wizardy stuff.
else:
msg = _('Changes saved.')
title = _('Contact Details')
button = _('Save')
data = {}
if waffle.flag_is_active(request, 'solitude-payments'):
data = client.get_seller_paypal_if_exists(addon) or {}
# TODO(solitude): remove this bit.
# If it's not in solitude, use the local version
adp, created = (AddonPaymentData.objects
.safer_get_or_create(addon=addon))
if not data:
data = model_to_dict(adp)
form = forms.PaypalPaymentData(request.POST or data)
if request.method == 'POST' and form.is_valid():
# TODO(solitude): we will remove this.
# Everything is finally set up so now save the paypal_id and token
# to the addon.
addon.update(paypal_id=request.session['unconfirmed_paypal_id'])
if waffle.flag_is_active(request, 'solitude-payments'):
obj = client.create_seller_paypal(addon)
client.patch_seller_paypal(pk=obj['resource_pk'],
data={'paypal_id': addon.paypal_id})
if waffle.flag_is_active(request, 'solitude-payments'):
# TODO(solitude): when the migration of data is completed, we
# will be able to remove this.
pk = client.create_seller_for_pay(addon)
client.patch_seller_paypal(pk=pk, data=form.cleaned_data)
# TODO(solitude): remove this.
adp.update(**form.cleaned_data)
messages.success(request, msg)
if source == 'paypal' and addon.is_incomplete() and addon.paypal_id:
addon.mark_done()
return redirect(addon.get_dev_url('payments'))
return jingo.render(request,
'developers/payments/paypal-details-confirm.html',
{'addon': addon, 'button': button, 'form': form,
'title': title})
@json_view
@dev_required(owner_for_post=True, webapp=True)
def paypal_setup_check(request, addon_id, addon, webapp):
if waffle.flag_is_active(request, 'solitude-payments'):
data = client.post_account_check(data={'seller': addon})
return {'valid': data['passed'], 'message': data['errors']}
else:
if not addon.paypal_id:
return {'valid': False, 'message': [_('No PayPal email.')]}
check = Check(addon=addon)
check.all()
return {'valid': check.passed, 'message': check.errors}
@json_view
@dev_required(owner_for_post=True, webapp=True)
def paypal_remove(request, addon_id, addon, webapp):
"""
Unregisters PayPal account from app.
"""
try:
addon.update(paypal_id='')
addonpremium, created = (AddonPremium.objects
.safer_get_or_create(addon=addon))
addonpremium.update(paypal_permissions_token='')
except Exception as e:
return {'success': False, 'error': [e]}
return {'success': True, 'error': []}
@waffle_switch('in-app-payments')
@dev_required(owner_for_post=True, webapp=True)
@transaction.commit_on_success
@ -583,8 +585,10 @@ def _premium(request, addon_id, addon, webapp=False):
premium_form.save()
messages.success(request, _('Changes successfully saved.'))
return redirect(addon.get_dev_url('payments'))
return jingo.render(request, 'developers/payments/premium.html',
dict(addon=addon, webapp=webapp, premium=addon.premium,
paypal_create_url=settings.PAYPAL_CGI_URL,
form=premium_form))

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

@ -1,15 +1,8 @@
from django import forms
import amo
from tower import ugettext as _
APP_UPSELL_CHOICES = (
(0, _("I don't have a free app to associate.")),
(1, _('This is a premium upgrade.')),
)
APP_PUBLIC_CHOICES = (
(0, _('As soon as it is approved.')),
(1, _('Not until I manually make it public.')),

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

@ -17,10 +17,8 @@ from market.models import AddonPremium, Price
from translations.widgets import TransInput, TransTextarea
from translations.fields import TransField
from mkt.developers.forms import (PaypalSetupForm as OriginalPaypalSetupForm,
verify_app_domain)
from mkt.site.forms import (AddonChoiceField, APP_UPSELL_CHOICES,
APP_PUBLIC_CHOICES)
from mkt.developers.forms import verify_app_domain
from mkt.site.forms import AddonChoiceField, APP_PUBLIC_CHOICES
class DevAgreementForm(happyforms.Form):
@ -63,18 +61,29 @@ class NewWebappForm(happyforms.Form):
_(u'Version %s already exists') % ver)
else:
# Throw an error if this is a dupe.
verify_app_domain(upload.name) # JS sets manifest as `upload.name`.
# (JS sets manifest as `upload.name`.)
verify_app_domain(upload.name)
return upload
class PaypalSetupForm(OriginalPaypalSetupForm):
class PaypalSetupForm(happyforms.Form):
business_account = forms.ChoiceField(widget=forms.RadioSelect, choices=[],
label=_(u'Do you already have a PayPal Premier or Business account?'))
email = forms.EmailField(required=False, label=_(u'PayPal email address'))
def __init__(self, *args, **kw):
super(PaypalSetupForm, self).__init__(*args, **kw)
self.fields['business_account'].choices = (
('yes', _lazy(u'Yes')),
('no', _lazy(u'No')),
('later', _lazy(u"I'll link my PayPal account later.")))
self.fields['business_account'].choices = (('yes', _lazy('Yes')),
('no', _lazy('No')),
('later', _lazy(u"I'll link my PayPal account later.")))
def clean(self):
data = self.cleaned_data
if data.get('business_account') == 'yes' and not data.get('email'):
msg = _(u'The PayPal email is required.')
self._errors['email'] = self.error_class([msg])
return data
class PremiumTypeForm(happyforms.Form):
@ -95,11 +104,6 @@ class UpsellForm(happyforms.Form):
'made available for sale?'),
coerce=int,
required=False)
do_upsell = forms.TypedChoiceField(coerce=lambda x: bool(int(x)),
choices=APP_UPSELL_CHOICES,
widget=forms.RadioSelect(),
label=_lazy(u'Upsell this app'),
required=False)
free = AddonChoiceField(queryset=Addon.objects.none(),
required=False,
empty_label='',

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

@ -1067,6 +1067,10 @@ class TestPayments(TestSubmit):
get_permissions_token):
self.webapp.update(premium_type=amo.ADDON_PREMIUM,
paypal_id='a@a.com')
session = self.client.session
session['unconfirmed_paypal_id'] = self.webapp.paypal_id
session.save()
get_permissions_token.return_value = 'foo'
get_personal_data.return_value = {'email': 'a@a.com'}
res = self.client.get(self.get_acquire_url())
@ -1100,10 +1104,14 @@ class TestPayments(TestSubmit):
get_permissions_token):
self.webapp.update(premium_type=amo.ADDON_PREMIUM,
paypal_id='b@b.com')
session = self.client.session
session['unconfirmed_paypal_id'] = self.webapp.paypal_id
session.save()
get_permissions_token.return_value = 'foo'
get_personal_data.return_value = {'email': 'a@a.com'}
res = self.client.get(self.get_acquire_url())
self.assert3xx(res, self.get_url('payments.paypal'))
self.assert3xx(res, self.get_url('payments'))
@mock.patch('paypal.get_permissions_token')
def test_bounce_result_fails_paypal_error(self, get_permissions_token):