Skip to content
This repository was archived by the owner on Mar 15, 2018. It is now read-only.

Commit 57eb235

Browse files
author
Allen Short
committed
add price and payment_account as settable fields on webapps. (bug 869557) (bug 869559)
1 parent b539b43 commit 57eb235

File tree

6 files changed

+196
-5
lines changed

6 files changed

+196
-5
lines changed

docs/api/topics/apps.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,15 @@ App
180180
refer to Android mobile and tablet. As opposed to Firefox OS.
181181
:param required premium_type: One of `free`, `premium`,
182182
`free-inapp`, `premium-inapp`, or `other`.
183+
:param optional price: The price for your app as a string, for example
184+
"0.10". Required for `premium` or `premium-inapp` apps.
185+
:param optional payment_account: The path for the
186+
:ref:`payment account <payment-account-label>` resource you want to
187+
associate with this app.
183188

184189
**Response**
185190

186-
:status 201: successfully updated.
191+
:status 202: successfully updated.
187192

188193

189194
.. http:delete:: /api/v1/apps/app/(int:id)/

docs/api/topics/payment.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Payments
77
This API is specific to setting up and processing payments for an app in the
88
Marketplace.
99

10+
.. _payment-account-label:
11+
1012
Configuring payment accounts
1113
============================
1214

mkt/api/resources.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from constants.applications import DEVICE_TYPES
2626
from files.models import FileUpload, Platform
2727
from lib.metrics import record_action
28+
from market.models import AddonPremium, Price
2829

2930
from mkt.api.authentication import (OptionalOAuthAuthentication,
3031
OAuthAuthentication)
@@ -38,6 +39,7 @@
3839
from mkt.carriers import get_carrier_id, CARRIERS, CARRIER_MAP
3940
from mkt.developers import tasks
4041
from mkt.developers.forms import NewManifestForm, PreviewForm
42+
from mkt.developers.models import AddonPaymentAccount
4143
from mkt.regions import get_region_id, get_region, REGIONS_DICT
4244
from mkt.submit.forms import AppDetailsBasicForm
4345
from mkt.webapps.utils import app_to_dict
@@ -109,14 +111,17 @@ def dehydrate(self, bundle):
109111

110112

111113
class AppResource(CORSResource, MarketplaceModelResource):
114+
payment_account = fields.ToOneField('mkt.developers.api.AccountResource',
115+
'app_payment_account', null=True)
116+
premium_type = fields.IntegerField(null=True)
112117
previews = fields.ToManyField('mkt.api.resources.PreviewResource',
113118
'previews', readonly=True)
114-
premium_type = fields.IntegerField()
115119

116120
class Meta(MarketplaceModelResource.Meta):
117121
queryset = Webapp.objects.all() # Gets overriden in dispatch.
118122
fields = ['categories', 'description', 'device_types', 'homepage',
119-
'id', 'name', 'premium_type', 'privacy_policy',
123+
'id', 'name', 'payment_account', 'premium_type',
124+
'privacy_policy',
120125
'status', 'summary', 'support_email', 'support_url']
121126
list_allowed_methods = ['get', 'post']
122127
detail_allowed_methods = ['get', 'put', 'delete']
@@ -241,7 +246,8 @@ def obj_update(self, bundle, request, **kwargs):
241246
data['app_slug'] = data.get('app_slug', obj.app_slug)
242247
data.update(self.formset(data))
243248
data.update(self.devices(data))
244-
self.hydrate_premium_type(bundle)
249+
self.update_premium_type(bundle)
250+
self.update_payment_account(bundle)
245251

246252
forms = [AppDetailsBasicForm(data, instance=obj, request=request),
247253
DeviceTypeForm(data, addon=obj),
@@ -258,6 +264,45 @@ def obj_update(self, bundle, request, **kwargs):
258264

259265
return bundle
260266

267+
def update_premium_type(self, bundle):
268+
self.hydrate_premium_type(bundle)
269+
if bundle.obj.premium_type != amo.ADDON_FREE:
270+
ap = AddonPremium.objects.safer_get_or_create(addon=bundle.obj)[0]
271+
else:
272+
ap = None
273+
if bundle.obj.premium_type in amo.ADDON_PREMIUMS:
274+
if not bundle.data.get('price') or not Price.objects.filter(
275+
price=bundle.data['price']).exists():
276+
tiers = ', '.join('"%s"' % p.get_price()
277+
for p in Price.objects.exclude(price="0.00"))
278+
raise fields.ApiFieldError(
279+
'Premium app specified without a valid price. price can be'
280+
' one of %s.' % (tiers,))
281+
else:
282+
ap.price = Price.objects.get(price=bundle.data['price'])
283+
ap.save()
284+
else:
285+
if ap:
286+
ap.price = Price.objects.get(price='0.00')
287+
ap.save()
288+
289+
def update_payment_account(self, bundle):
290+
if 'payment_account' in bundle.data:
291+
if bundle.obj.premium_type == amo.ADDON_FREE:
292+
raise fields.ApiFieldError(
293+
'Free apps cannot have payment accounts.')
294+
acct = self.fields['payment_account'].hydrate(bundle).obj
295+
try:
296+
log.info('[1@%s] Deleting app payment account' % bundle.obj.pk)
297+
AddonPaymentAccount.objects.get(addon=bundle.obj).delete()
298+
except AddonPaymentAccount.DoesNotExist:
299+
pass
300+
301+
log.info('[1@%s] Creating new app payment account' % bundle.obj.pk)
302+
AddonPaymentAccount.create(
303+
provider='bango', addon=bundle.obj,
304+
payment_account=acct)
305+
261306
def dehydrate(self, bundle):
262307
obj = bundle.obj
263308
user = getattr(bundle.request, 'user', None)

mkt/api/tests/test_handlers.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
AddonUser, Category, Flag, Preview)
1212
from amo.tests import AMOPaths, app_factory
1313
from files.models import FileUpload
14+
from market.models import Price, AddonPremium
1415
from users.models import UserProfile
1516

16-
from mkt.api.tests.test_oauth import BaseOAuth
17+
from mkt.api.tests.test_oauth import BaseOAuth, get_absolute_url
1718
from mkt.api.base import get_url, list_url
1819
from mkt.constants import APP_IMAGE_SIZES, carriers, regions
20+
from mkt.developers.models import (AddonPaymentAccount, PaymentAccount,
21+
SolitudeSeller)
22+
from mkt.developers.tests.test_api import payment_data
1923
from mkt.site.fixtures import fixture
2024
from mkt.webapps.models import ContentRating, ImageAsset, Webapp
2125
from reviews.models import Review
@@ -533,6 +537,130 @@ def test_put_desktop_error_nice(self):
533537
assert '12345' in self.get_error(res)['device_types'][0], (
534538
self.get_error(res))
535539

540+
def test_put_price(self):
541+
app = self.create_app()
542+
data = self.base_data()
543+
Price.objects.create(price='1.07')
544+
data['premium_type'] = 'premium'
545+
data['price'] = "1.07"
546+
res = self.client.put(self.get_url, data=json.dumps(data))
547+
eq_(res.status_code, 202)
548+
eq_(str(app.addonpremium.price.get_price()), "1.07")
549+
550+
def test_put_premium_inapp(self):
551+
app = self.create_app()
552+
data = self.base_data()
553+
Price.objects.create(price='1.07')
554+
data['premium_type'] = 'premium-inapp'
555+
data['price'] = "1.07"
556+
res = self.client.put(self.get_url, data=json.dumps(data))
557+
eq_(res.status_code, 202)
558+
eq_(str(app.addonpremium.price.get_price()), "1.07")
559+
eq_(Webapp.objects.get(pk=app.pk).premium_type,
560+
amo.ADDON_PREMIUM_INAPP)
561+
562+
def test_put_bad_price(self):
563+
self.create_app()
564+
data = self.base_data()
565+
Price.objects.create(price='1.07')
566+
Price.objects.create(price='3.14')
567+
data['premium_type'] = 'premium'
568+
data['price'] = "2.03"
569+
res = self.client.put(self.get_url, data=json.dumps(data))
570+
eq_(res.status_code, 400)
571+
eq_(res.content,
572+
'Premium app specified without a valid price. price can be one of '
573+
'"1.07", "3.14".')
574+
575+
def test_put_no_price(self):
576+
self.create_app()
577+
data = self.base_data()
578+
Price.objects.create(price='1.07')
579+
Price.objects.create(price='3.14')
580+
data['premium_type'] = 'premium'
581+
res = self.client.put(self.get_url, data=json.dumps(data))
582+
eq_(res.status_code, 400)
583+
eq_(res.content,
584+
'Premium app specified without a valid price. price can be one of '
585+
'"1.07", "3.14".')
586+
587+
def test_put_free_inapp(self):
588+
app = self.create_app()
589+
data = self.base_data()
590+
Price.objects.create(price='0.00')
591+
Price.objects.create(price='3.14')
592+
data['premium_type'] = 'free-inapp'
593+
res = self.client.put(self.get_url, data=json.dumps(data))
594+
eq_(res.status_code, 202)
595+
eq_(str(app.addonpremium.get_price()), "0.00")
596+
597+
@patch('mkt.developers.models.client')
598+
def test_get_payment_account(self, client):
599+
client.api.bango.package().get.return_value = {"full": payment_data}
600+
app = self.create_app()
601+
app.premium_type = amo.ADDON_PREMIUM
602+
app.save()
603+
seller = SolitudeSeller.objects.create(user=self.profile, uuid='uid')
604+
acct = PaymentAccount.objects.create(
605+
user=self.profile, solitude_seller=seller, agreed_tos=True,
606+
seller_uri='uri', uri='uri', bango_package_id=123)
607+
AddonPaymentAccount.objects.create(
608+
addon=app, payment_account=acct, set_price=1,
609+
product_uri="/path/to/app/")
610+
p = Price.objects.create(price='1.07')
611+
AddonPremium.objects.create(addon=app, price=p)
612+
acct_url = get_absolute_url(get_url('account', acct.pk),
613+
'payments', False)
614+
res = self.client.get(self.get_url)
615+
eq_(res.status_code, 200)
616+
data = json.loads(res.content)
617+
eq_(data['payment_account'], acct_url)
618+
eq_(data['price'], '1.07')
619+
620+
@patch('mkt.developers.models.client')
621+
def test_put_payment_account(self, client):
622+
client.api.bango.package().get.return_value = {"full": payment_data}
623+
app = self.create_app()
624+
data = self.base_data()
625+
seller = SolitudeSeller.objects.create(user=self.profile, uuid='uid')
626+
acct = PaymentAccount.objects.create(
627+
user=self.profile, solitude_seller=seller, agreed_tos=True,
628+
seller_uri='uri', uri='uri', bango_package_id=123)
629+
Price.objects.create(price='1.07')
630+
data['price'] = "1.07"
631+
data['premium_type'] = 'premium'
632+
data['payment_account'] = get_absolute_url(get_url('account', acct.pk),
633+
'payments', False)
634+
res = self.client.put(self.get_url, data=json.dumps(data))
635+
eq_(res.status_code, 202)
636+
eq_(app.app_payment_account.payment_account.pk, acct.pk)
637+
638+
@patch('mkt.developers.models.client')
639+
def test_put_payment_account_on_free(self, client):
640+
client.api.bango.package().get.return_value = {"full": payment_data}
641+
self.create_app()
642+
data = self.base_data()
643+
seller = SolitudeSeller.objects.create(user=self.profile, uuid='uid')
644+
acct = PaymentAccount.objects.create(
645+
user=self.profile, solitude_seller=seller, agreed_tos=True,
646+
seller_uri='uri', uri='uri', bango_package_id=123)
647+
data['payment_account'] = get_absolute_url(get_url('account', acct.pk),
648+
'payments', False)
649+
res = self.client.put(self.get_url, data=json.dumps(data))
650+
eq_(res.status_code, 400)
651+
652+
def test_put_bogus_payment_account(self):
653+
app = self.create_app()
654+
data = self.base_data()
655+
Price.objects.create(price='1.07')
656+
data['price'] = "1.07"
657+
data['premium_type'] = 'premium'
658+
data['payment_account'] = get_absolute_url(get_url('account', 999),
659+
'payments', False)
660+
res = self.client.put(self.get_url, data=json.dumps(data))
661+
eq_(res.status_code, 400)
662+
assert not hasattr(app, 'app_payment_account')
663+
536664
def test_put_not_mine(self):
537665
obj = self.create_app()
538666
obj.authors.clear()

mkt/developers/tests/test_api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ def test_get(self, client):
119119
pkg['resource_uri'] = '/api/v1/payments/account/%s/' % self.account.pk
120120
eq_(data, pkg)
121121

122+
def test_only_get_by_owner(self, client):
123+
r = self.anon.get(get_url('account', self.account.pk))
124+
eq_(r.status_code, 401)
125+
122126
def test_put(self, client):
123127
addr = 'b@b.com'
124128
newpkg = package_data.copy()

mkt/webapps/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def app_to_dict(app, currency=None, user=None):
4040
"""Return app data as dict for API."""
4141
# Sad circular import issues.
4242
from mkt.api.resources import PreviewResource
43+
from mkt.developers.api import AccountResource
44+
from mkt.developers.models import AddonPaymentAccount
45+
4346

4447
cv = app.current_version
4548
version_data = {
@@ -75,6 +78,10 @@ def app_to_dict(app, currency=None, user=None):
7578
}
7679

7780
if app.premium:
81+
q = AddonPaymentAccount.objects.filter(addon=app)
82+
if len(q) > 0 and q[0].payment_account:
83+
data['payment_account'] = AccountResource().get_resource_uri(
84+
q[0].payment_account)
7885
try:
7986
data['price'] = app.get_price(currency)
8087
data['price_locale'] = app.get_price_locale(currency)

0 commit comments

Comments
 (0)