add `price` and `payment_account` as settable fields on webapps. (bug 869557) (bug 869559)
This commit is contained in:
Родитель
b539b43b09
Коммит
57eb235d45
|
@ -180,10 +180,15 @@ App
|
|||
refer to Android mobile and tablet. As opposed to Firefox OS.
|
||||
:param required premium_type: One of `free`, `premium`,
|
||||
`free-inapp`, `premium-inapp`, or `other`.
|
||||
:param optional price: The price for your app as a string, for example
|
||||
"0.10". Required for `premium` or `premium-inapp` apps.
|
||||
:param optional payment_account: The path for the
|
||||
:ref:`payment account <payment-account-label>` resource you want to
|
||||
associate with this app.
|
||||
|
||||
**Response**
|
||||
|
||||
:status 201: successfully updated.
|
||||
:status 202: successfully updated.
|
||||
|
||||
|
||||
.. http:delete:: /api/v1/apps/app/(int:id)/
|
||||
|
|
|
@ -7,6 +7,8 @@ Payments
|
|||
This API is specific to setting up and processing payments for an app in the
|
||||
Marketplace.
|
||||
|
||||
.. _payment-account-label:
|
||||
|
||||
Configuring payment accounts
|
||||
============================
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ from amo.utils import no_translation
|
|||
from constants.applications import DEVICE_TYPES
|
||||
from files.models import FileUpload, Platform
|
||||
from lib.metrics import record_action
|
||||
from market.models import AddonPremium, Price
|
||||
|
||||
from mkt.api.authentication import (OptionalOAuthAuthentication,
|
||||
OAuthAuthentication)
|
||||
|
@ -38,6 +39,7 @@ from mkt.api.http import HttpLegallyUnavailable
|
|||
from mkt.carriers import get_carrier_id, CARRIERS, CARRIER_MAP
|
||||
from mkt.developers import tasks
|
||||
from mkt.developers.forms import NewManifestForm, PreviewForm
|
||||
from mkt.developers.models import AddonPaymentAccount
|
||||
from mkt.regions import get_region_id, get_region, REGIONS_DICT
|
||||
from mkt.submit.forms import AppDetailsBasicForm
|
||||
from mkt.webapps.utils import app_to_dict
|
||||
|
@ -109,14 +111,17 @@ class ValidationResource(CORSResource, MarketplaceModelResource):
|
|||
|
||||
|
||||
class AppResource(CORSResource, MarketplaceModelResource):
|
||||
payment_account = fields.ToOneField('mkt.developers.api.AccountResource',
|
||||
'app_payment_account', null=True)
|
||||
premium_type = fields.IntegerField(null=True)
|
||||
previews = fields.ToManyField('mkt.api.resources.PreviewResource',
|
||||
'previews', readonly=True)
|
||||
premium_type = fields.IntegerField()
|
||||
|
||||
class Meta(MarketplaceModelResource.Meta):
|
||||
queryset = Webapp.objects.all() # Gets overriden in dispatch.
|
||||
fields = ['categories', 'description', 'device_types', 'homepage',
|
||||
'id', 'name', 'premium_type', 'privacy_policy',
|
||||
'id', 'name', 'payment_account', 'premium_type',
|
||||
'privacy_policy',
|
||||
'status', 'summary', 'support_email', 'support_url']
|
||||
list_allowed_methods = ['get', 'post']
|
||||
detail_allowed_methods = ['get', 'put', 'delete']
|
||||
|
@ -241,7 +246,8 @@ class AppResource(CORSResource, MarketplaceModelResource):
|
|||
data['app_slug'] = data.get('app_slug', obj.app_slug)
|
||||
data.update(self.formset(data))
|
||||
data.update(self.devices(data))
|
||||
self.hydrate_premium_type(bundle)
|
||||
self.update_premium_type(bundle)
|
||||
self.update_payment_account(bundle)
|
||||
|
||||
forms = [AppDetailsBasicForm(data, instance=obj, request=request),
|
||||
DeviceTypeForm(data, addon=obj),
|
||||
|
@ -258,6 +264,45 @@ class AppResource(CORSResource, MarketplaceModelResource):
|
|||
|
||||
return bundle
|
||||
|
||||
def update_premium_type(self, bundle):
|
||||
self.hydrate_premium_type(bundle)
|
||||
if bundle.obj.premium_type != amo.ADDON_FREE:
|
||||
ap = AddonPremium.objects.safer_get_or_create(addon=bundle.obj)[0]
|
||||
else:
|
||||
ap = None
|
||||
if bundle.obj.premium_type in amo.ADDON_PREMIUMS:
|
||||
if not bundle.data.get('price') or not Price.objects.filter(
|
||||
price=bundle.data['price']).exists():
|
||||
tiers = ', '.join('"%s"' % p.get_price()
|
||||
for p in Price.objects.exclude(price="0.00"))
|
||||
raise fields.ApiFieldError(
|
||||
'Premium app specified without a valid price. price can be'
|
||||
' one of %s.' % (tiers,))
|
||||
else:
|
||||
ap.price = Price.objects.get(price=bundle.data['price'])
|
||||
ap.save()
|
||||
else:
|
||||
if ap:
|
||||
ap.price = Price.objects.get(price='0.00')
|
||||
ap.save()
|
||||
|
||||
def update_payment_account(self, bundle):
|
||||
if 'payment_account' in bundle.data:
|
||||
if bundle.obj.premium_type == amo.ADDON_FREE:
|
||||
raise fields.ApiFieldError(
|
||||
'Free apps cannot have payment accounts.')
|
||||
acct = self.fields['payment_account'].hydrate(bundle).obj
|
||||
try:
|
||||
log.info('[1@%s] Deleting app payment account' % bundle.obj.pk)
|
||||
AddonPaymentAccount.objects.get(addon=bundle.obj).delete()
|
||||
except AddonPaymentAccount.DoesNotExist:
|
||||
pass
|
||||
|
||||
log.info('[1@%s] Creating new app payment account' % bundle.obj.pk)
|
||||
AddonPaymentAccount.create(
|
||||
provider='bango', addon=bundle.obj,
|
||||
payment_account=acct)
|
||||
|
||||
def dehydrate(self, bundle):
|
||||
obj = bundle.obj
|
||||
user = getattr(bundle.request, 'user', None)
|
||||
|
|
|
@ -11,11 +11,15 @@ from addons.models import (Addon, AddonDeviceType, AddonUpsell,
|
|||
AddonUser, Category, Flag, Preview)
|
||||
from amo.tests import AMOPaths, app_factory
|
||||
from files.models import FileUpload
|
||||
from market.models import Price, AddonPremium
|
||||
from users.models import UserProfile
|
||||
|
||||
from mkt.api.tests.test_oauth import BaseOAuth
|
||||
from mkt.api.tests.test_oauth import BaseOAuth, get_absolute_url
|
||||
from mkt.api.base import get_url, list_url
|
||||
from mkt.constants import APP_IMAGE_SIZES, carriers, regions
|
||||
from mkt.developers.models import (AddonPaymentAccount, PaymentAccount,
|
||||
SolitudeSeller)
|
||||
from mkt.developers.tests.test_api import payment_data
|
||||
from mkt.site.fixtures import fixture
|
||||
from mkt.webapps.models import ContentRating, ImageAsset, Webapp
|
||||
from reviews.models import Review
|
||||
|
@ -533,6 +537,130 @@ class TestAppCreateHandler(CreateHandler, AMOPaths):
|
|||
assert '12345' in self.get_error(res)['device_types'][0], (
|
||||
self.get_error(res))
|
||||
|
||||
def test_put_price(self):
|
||||
app = self.create_app()
|
||||
data = self.base_data()
|
||||
Price.objects.create(price='1.07')
|
||||
data['premium_type'] = 'premium'
|
||||
data['price'] = "1.07"
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 202)
|
||||
eq_(str(app.addonpremium.price.get_price()), "1.07")
|
||||
|
||||
def test_put_premium_inapp(self):
|
||||
app = self.create_app()
|
||||
data = self.base_data()
|
||||
Price.objects.create(price='1.07')
|
||||
data['premium_type'] = 'premium-inapp'
|
||||
data['price'] = "1.07"
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 202)
|
||||
eq_(str(app.addonpremium.price.get_price()), "1.07")
|
||||
eq_(Webapp.objects.get(pk=app.pk).premium_type,
|
||||
amo.ADDON_PREMIUM_INAPP)
|
||||
|
||||
def test_put_bad_price(self):
|
||||
self.create_app()
|
||||
data = self.base_data()
|
||||
Price.objects.create(price='1.07')
|
||||
Price.objects.create(price='3.14')
|
||||
data['premium_type'] = 'premium'
|
||||
data['price'] = "2.03"
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 400)
|
||||
eq_(res.content,
|
||||
'Premium app specified without a valid price. price can be one of '
|
||||
'"1.07", "3.14".')
|
||||
|
||||
def test_put_no_price(self):
|
||||
self.create_app()
|
||||
data = self.base_data()
|
||||
Price.objects.create(price='1.07')
|
||||
Price.objects.create(price='3.14')
|
||||
data['premium_type'] = 'premium'
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 400)
|
||||
eq_(res.content,
|
||||
'Premium app specified without a valid price. price can be one of '
|
||||
'"1.07", "3.14".')
|
||||
|
||||
def test_put_free_inapp(self):
|
||||
app = self.create_app()
|
||||
data = self.base_data()
|
||||
Price.objects.create(price='0.00')
|
||||
Price.objects.create(price='3.14')
|
||||
data['premium_type'] = 'free-inapp'
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 202)
|
||||
eq_(str(app.addonpremium.get_price()), "0.00")
|
||||
|
||||
@patch('mkt.developers.models.client')
|
||||
def test_get_payment_account(self, client):
|
||||
client.api.bango.package().get.return_value = {"full": payment_data}
|
||||
app = self.create_app()
|
||||
app.premium_type = amo.ADDON_PREMIUM
|
||||
app.save()
|
||||
seller = SolitudeSeller.objects.create(user=self.profile, uuid='uid')
|
||||
acct = PaymentAccount.objects.create(
|
||||
user=self.profile, solitude_seller=seller, agreed_tos=True,
|
||||
seller_uri='uri', uri='uri', bango_package_id=123)
|
||||
AddonPaymentAccount.objects.create(
|
||||
addon=app, payment_account=acct, set_price=1,
|
||||
product_uri="/path/to/app/")
|
||||
p = Price.objects.create(price='1.07')
|
||||
AddonPremium.objects.create(addon=app, price=p)
|
||||
acct_url = get_absolute_url(get_url('account', acct.pk),
|
||||
'payments', False)
|
||||
res = self.client.get(self.get_url)
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(data['payment_account'], acct_url)
|
||||
eq_(data['price'], '1.07')
|
||||
|
||||
@patch('mkt.developers.models.client')
|
||||
def test_put_payment_account(self, client):
|
||||
client.api.bango.package().get.return_value = {"full": payment_data}
|
||||
app = self.create_app()
|
||||
data = self.base_data()
|
||||
seller = SolitudeSeller.objects.create(user=self.profile, uuid='uid')
|
||||
acct = PaymentAccount.objects.create(
|
||||
user=self.profile, solitude_seller=seller, agreed_tos=True,
|
||||
seller_uri='uri', uri='uri', bango_package_id=123)
|
||||
Price.objects.create(price='1.07')
|
||||
data['price'] = "1.07"
|
||||
data['premium_type'] = 'premium'
|
||||
data['payment_account'] = get_absolute_url(get_url('account', acct.pk),
|
||||
'payments', False)
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 202)
|
||||
eq_(app.app_payment_account.payment_account.pk, acct.pk)
|
||||
|
||||
@patch('mkt.developers.models.client')
|
||||
def test_put_payment_account_on_free(self, client):
|
||||
client.api.bango.package().get.return_value = {"full": payment_data}
|
||||
self.create_app()
|
||||
data = self.base_data()
|
||||
seller = SolitudeSeller.objects.create(user=self.profile, uuid='uid')
|
||||
acct = PaymentAccount.objects.create(
|
||||
user=self.profile, solitude_seller=seller, agreed_tos=True,
|
||||
seller_uri='uri', uri='uri', bango_package_id=123)
|
||||
data['payment_account'] = get_absolute_url(get_url('account', acct.pk),
|
||||
'payments', False)
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 400)
|
||||
|
||||
def test_put_bogus_payment_account(self):
|
||||
app = self.create_app()
|
||||
data = self.base_data()
|
||||
Price.objects.create(price='1.07')
|
||||
data['price'] = "1.07"
|
||||
data['premium_type'] = 'premium'
|
||||
data['payment_account'] = get_absolute_url(get_url('account', 999),
|
||||
'payments', False)
|
||||
res = self.client.put(self.get_url, data=json.dumps(data))
|
||||
eq_(res.status_code, 400)
|
||||
assert not hasattr(app, 'app_payment_account')
|
||||
|
||||
def test_put_not_mine(self):
|
||||
obj = self.create_app()
|
||||
obj.authors.clear()
|
||||
|
|
|
@ -119,6 +119,10 @@ class AccountTests(BaseOAuth):
|
|||
pkg['resource_uri'] = '/api/v1/payments/account/%s/' % self.account.pk
|
||||
eq_(data, pkg)
|
||||
|
||||
def test_only_get_by_owner(self, client):
|
||||
r = self.anon.get(get_url('account', self.account.pk))
|
||||
eq_(r.status_code, 401)
|
||||
|
||||
def test_put(self, client):
|
||||
addr = 'b@b.com'
|
||||
newpkg = package_data.copy()
|
||||
|
|
|
@ -40,6 +40,9 @@ def app_to_dict(app, currency=None, user=None):
|
|||
"""Return app data as dict for API."""
|
||||
# Sad circular import issues.
|
||||
from mkt.api.resources import PreviewResource
|
||||
from mkt.developers.api import AccountResource
|
||||
from mkt.developers.models import AddonPaymentAccount
|
||||
|
||||
|
||||
cv = app.current_version
|
||||
version_data = {
|
||||
|
@ -75,6 +78,10 @@ def app_to_dict(app, currency=None, user=None):
|
|||
}
|
||||
|
||||
if app.premium:
|
||||
q = AddonPaymentAccount.objects.filter(addon=app)
|
||||
if len(q) > 0 and q[0].payment_account:
|
||||
data['payment_account'] = AccountResource().get_resource_uri(
|
||||
q[0].payment_account)
|
||||
try:
|
||||
data['price'] = app.get_price(currency)
|
||||
data['price_locale'] = app.get_price_locale(currency)
|
||||
|
|
Загрузка…
Ссылка в новой задаче