Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

require a tier, not prices (bug 821511) #18

Closed
wants to merge 1 commit into from

3 participants

Andy McKay Kumar McMillan Christopher Van
Andy McKay
Owner

No description provided.

Kumar McMillan kumar303 commented on the diff December 20, 2012
lib/marketplace/api.py
((9 lines not shown))
4 9
 
5 10
 
6 11
 class MarketplaceAPI(SlumberWrapper):
7 12
     errors = {}
8 13
 
9 14
     def get_price(self, tier):
10  
-        return self.slumber.api.webpay.prices(id=tier).get()
11  
-
  15
+        # TODO: cache this.
  16
+        try:
  17
+            return self.slumber.api.webpay.prices(id=tier).get()
  18
+        except HttpClientError, err:
  19
+            if err.response.status_code:
  20
+                raise TierNotFound
1
Kumar McMillan Owner

could you add some info to this exception message so we know what tier couldn't be found? That way we'll see it immediately in sentry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
webpay/pay/samples.py
... ...
@@ -27,10 +27,7 @@ def payload(self, iss=None, aud=None, exp=None, iat=None,
27 27
             exp = iat + 3600  # Expires in 1 hour.
28 28
 
29 29
         req = {
30  
-            'price': [{
31  
-                'amount': '0.99',
32  
-                'currency': 'USD',
33  
-            }],
  30
+            'tier': 1,
1
Kumar McMillan Owner

Hmm. I think we should call this pricePoint in the public nav.mozPay() API because that's more in line with mobile speak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
webpay/pay/views.py
((7 lines not shown))
52 53
                            'request.name',
53 54
                            'request.description'))
54 55
     except (TypeError, InvalidJWT, RequestExpired), exc:
55 56
         log.exception('calling verify_jwt')
56 57
         return _error(request, exception=exc)
57 58
 
  59
+    # Assert price tier is valid.
  60
+    try:
  61
+        client.get_price(pay_req['request']['tier'])
  62
+    except (TierNotFound, HttpClientError), exc:
  63
+        log.exception('calling verifying tier')
  64
+        return _error(request, exception=exc)
  65
+
1
Kumar McMillan Owner

I like this. It will help on our test servers to show errors to devs immediately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Kumar McMillan
Owner

r+wc

Christopher Van
>>> sorted('client, HttpClientError, TierNotFound'.split(', '))
['HttpClientError', 'TierNotFound', 'client']
Christopher Van

"lookup" is a noun. "look up" is a verb.

Andy McKay andymckay closed this January 04, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Dec 20, 2012
Andy McKay require a tier, not prices (bug 821511) 1a7d57a
This page is out of date. Refresh to see the latest.
14  lib/marketplace/api.py
... ...
@@ -1,13 +1,23 @@
1 1
 from django.conf import settings
2 2
 
3 3
 from ..utils import SlumberWrapper
  4
+from slumber.exceptions import HttpClientError
  5
+
  6
+
  7
+class TierNotFound(Exception):
  8
+    pass
4 9
 
5 10
 
6 11
 class MarketplaceAPI(SlumberWrapper):
7 12
     errors = {}
8 13
 
9 14
     def get_price(self, tier):
10  
-        return self.slumber.api.webpay.prices(id=tier).get()
11  
-
  15
+        # TODO: cache this.
  16
+        try:
  17
+            return self.slumber.api.webpay.prices(id=tier).get()
  18
+        except HttpClientError, err:
  19
+            if err.response.status_code:
  20
+                raise TierNotFound(tier)
  21
+            raise
12 22
 
13 23
 client = MarketplaceAPI(settings.MARKETPLACE_URL or 'http://example.com')
11  lib/solitude/api.py
@@ -125,9 +125,9 @@ def verify_pin(self, uuid, pin):
125 125
     def configure_product_for_billing(self, webpay_trans_id,
126 126
                                       seller_uuid,
127 127
                                       product_id, product_name,
128  
-                                      currency, amount,
129 128
                                       redirect_url_onsuccess,
130  
-                                      redirect_url_onerror):
  129
+                                      redirect_url_onerror,
  130
+                                      prices):
131 131
         """
132 132
         Get the billing configuration ID for a Bango transaction.
133 133
         """
@@ -145,7 +145,9 @@ def configure_product_for_billing(self, webpay_trans_id,
145 145
         )
146 146
         if res['meta']['total_count'] == 0:
147 147
             bango_product_uri = self.create_product(product_id,
148  
-                    product_name, currency, amount, res['objects'][0])
  148
+                    # TODO: look at why we need currency and price for the
  149
+                    # premium call. This might be a Bango issue.
  150
+                    product_name, 1, 'EUR', res['objects'][0])
149 151
         else:
150 152
             bango_product_uri = res['objects'][0]['resource_uri']
151 153
             log.info('transaction %s: bango product: %s'
@@ -153,8 +155,7 @@ def configure_product_for_billing(self, webpay_trans_id,
153 155
 
154 156
         res = self.slumber.bango.billing.post({
155 157
             'pageTitle': product_name,
156  
-            'price_currency': currency,
157  
-            'price_amount': str(amount),
  158
+            'prices': prices,
158 159
             'seller_product_bango': bango_product_uri,
159 160
             'redirect_url_onsuccess': redirect_url_onsuccess,
160 161
             'redirect_url_onerror': redirect_url_onerror,
5  webpay/pay/samples.py
@@ -27,10 +27,7 @@ def payload(self, iss=None, aud=None, exp=None, iat=None,
27 27
             exp = iat + 3600  # Expires in 1 hour.
28 28
 
29 29
         req = {
30  
-            'price': [{
31  
-                'amount': '0.99',
32  
-                'currency': 'USD',
33  
-            }],
  30
+            'pricePoint': 1,
34 31
             'id': 'some-generated-unique-id',
35 32
             'name': 'My bands latest album',
36 33
             'description': '320kbps MP3 download, DRM free!',
7  webpay/pay/tasks.py
@@ -11,6 +11,7 @@
11 11
 
12 12
 from celeryutils import task
13 13
 import jwt
  14
+from lib.marketplace.api import client as mkt_client
14 15
 from lib.solitude.api import client
15 16
 from multidb.pinning import use_master
16 17
 
@@ -32,7 +33,7 @@ class TransactionOutOfSync(Exception):
32 33
 @task
33 34
 @use_master
34 35
 @transaction.commit_on_success
35  
-def start_pay(trans_id, **kw):
  36
+def start_pay(trans_id, pay_request, **kw):
36 37
     """
37 38
     Work with Solitude to begin a Bango payment.
38 39
 
@@ -62,15 +63,15 @@ def start_pay(trans_id, **kw):
62 63
             log.info('Using real seller_uuid %r for Marketplace %r '
63 64
                      'app payment' % (seller_uuid, settings.KEY))
64 65
 
  66
+        prices = mkt_client.get_price(pay_request['request']['pricePoint'])
65 67
         bill_id = client.configure_product_for_billing(
66 68
             trans.pk,
67 69
             seller_uuid,
68 70
             trans.product_id,
69 71
             trans.name,  # app/product name
70  
-            trans.currency,
71  
-            trans.amount,
72 72
             absolutify(reverse('bango.success')),
73 73
             absolutify(reverse('bango.error')),
  74
+            prices['prices']
74 75
         )
75 76
         trans.bango_config_id = bill_id
76 77
         trans.state = TRANS_STATE_READY
59  webpay/pay/tests/test_tasks.py
@@ -10,6 +10,7 @@
10 10
 import fudge
11 11
 from fudge.inspector import arg
12 12
 import jwt
  13
+from lib.marketplace.api import TierNotFound
13 14
 from lib.solitude import api
14 15
 import mock
15 16
 from nose.tools import eq_, raises
@@ -50,8 +51,8 @@ def setUp(self):
50 51
             state=TRANS_STATE_COMPLETED,
51 52
             issuer=self.iss,
52 53
             issuer_key=self.iss.issuer_key,
53  
-            amount=Decimal(app_payment['request']['price'][0]['amount']),
54  
-            currency=app_payment['request']['price'][0]['currency'],
  54
+            amount=1,  # Temporary until we get rid of transactions.
  55
+            currency='USD',  # This too.
55 56
             name=app_payment['request']['name'],
56 57
             description=app_payment['request']['description'],
57 58
             json_request=json.dumps(app_payment))
@@ -233,10 +234,7 @@ def is_valid(payload):
233 234
             eq_(data['iss'], settings.NOTIFY_ISSUER)
234 235
             eq_(data['aud'], self.iss.issuer_key)
235 236
             eq_(data['typ'], 'mozilla/payments/pay/postback/v1')
236  
-            eq_(data['request']['price'][0]['amount'],
237  
-                app_payment['request']['price'][0]['amount'])
238  
-            eq_(data['request']['price'][0]['currency'],
239  
-                app_payment['request']['price'][0]['currency'])
  237
+            eq_(data['request']['pricePoint'], 1)
240 238
             eq_(data['request']['name'], app_payment['request']['name'])
241 239
             eq_(data['request']['description'],
242 240
                 app_payment['request']['description'])
@@ -272,12 +270,14 @@ def setUp(self):
272 270
             description='A sword to use in Awesome Castle Game',
273 271
             json_request='{}',
274 272
         )
  273
+        self.payload = {'request': {'pricePoint': 1}}
  274
+        self.prices = {'prices': {'amount': 1, 'currency': 'EUR'}}
275 275
 
276 276
     def get_trans(self):
277 277
         return Transaction.objects.get(pk=self.trans.pk)
278 278
 
279 279
     def start(self):
280  
-        tasks.start_pay(self.trans.pk)
  280
+        tasks.start_pay(self.trans.pk, self.payload)
281 281
 
282 282
     def update(self, **kw):
283 283
         Transaction.objects.filter(pk=self.trans.pk).update(**kw)
@@ -304,14 +304,18 @@ def test_already_failed(self):
304 304
 
305 305
     @raises(api.SellerNotConfigured)
306 306
     @mock.patch('lib.solitude.api.client.slumber')
307  
-    def test_no_seller(self, slumber):
308  
-        slumber.generic.seller.get.return_value = {'meta': {'total_count': 0}}
  307
+    @mock.patch('lib.marketplace.api.client.slumber')
  308
+    def test_no_seller(self,  marketplace, solitude):
  309
+        marketplace.webpay.prices.return_value = self.prices
  310
+        solitude.generic.seller.get.return_value = {'meta': {'total_count': 0}}
309 311
         self.start()
310 312
         eq_(self.get_trans().state, TRANS_STATE_FAILED)
311 313
 
312 314
     @mock.patch('lib.solitude.api.client.slumber')
313  
-    def test_existing_product(self, slumber):
314  
-        slumber.generic.seller.get.return_value = {
  315
+    @mock.patch('lib.marketplace.api.client.slumber')
  316
+    def test_existing_product(self, marketplace, solitude):
  317
+        marketplace.webpay.prices.return_value = self.prices
  318
+        solitude.generic.seller.get.return_value = {
315 319
             'meta': {'total_count': 1},
316 320
             'objects': [{
317 321
                 'resource_pk': 29,
@@ -319,7 +323,7 @@ def test_existing_product(self, slumber):
319 323
                 'resource_uri': '/generic/seller/29/'
320 324
             }]
321 325
         }
322  
-        slumber.bango.product.get.return_value = {
  326
+        solitude.bango.product.get.return_value = {
323 327
             'meta': {'total_count': 1},
324 328
             'objects': [{
325 329
                 'resource_pk': 15,
@@ -332,15 +336,34 @@ def test_existing_product(self, slumber):
332 336
                 '/bango/product/15/'
333 337
             }]
334 338
         }
335  
-        self.set_billing_id(slumber, 123)
  339
+        self.set_billing_id(solitude, 123)
336 340
         self.start()
337 341
         trans = self.get_trans()
338 342
         eq_(trans.state, TRANS_STATE_READY)
339 343
         eq_(trans.bango_config_id, 123)
340 344
 
341 345
     @mock.patch('lib.solitude.api.client.slumber')
  346
+    @mock.patch('lib.marketplace.api.client.slumber')
  347
+    def test_price_used(self, marketplace, solitude):
  348
+        prices = mock.Mock()
  349
+        prices.get.return_value = self.prices
  350
+        marketplace.api.webpay.prices.return_value = prices
  351
+        self.set_billing_id(solitude, 123)
  352
+        self.start()
  353
+        eq_(solitude.bango.billing.post.call_args[0][0]['prices'],
  354
+            self.prices['prices'])
  355
+
  356
+    @mock.patch('lib.solitude.api.client.slumber')
  357
+    @mock.patch('lib.marketplace.api.client.slumber')
  358
+    def test_price_fails(self, marketplace, solitude):
  359
+        marketplace.api.webpay.prices.side_effect = TierNotFound
  360
+        with self.assertRaises(TierNotFound):
  361
+            self.start()
  362
+
  363
+    @mock.patch('lib.solitude.api.client.slumber')
  364
+    @mock.patch('lib.marketplace.api.client.slumber')
342 365
     @raises(RuntimeError)
343  
-    def test_exception_fails_transaction(self, slumber):
  366
+    def test_exception_fails_transaction(self, marketplace, slumber):
344 367
         slumber.generic.seller.get.side_effect = RuntimeError
345 368
         self.start()
346 369
         trans = self.get_trans()
@@ -348,8 +371,10 @@ def test_exception_fails_transaction(self, slumber):
348 371
 
349 372
     @mock.patch.object(settings, 'KEY', 'marketplace-domain')
350 373
     @mock.patch('lib.solitude.api.client.slumber')
351  
-    def test_marketplace_seller_switch(self, slumber):
352  
-        self.set_billing_id(slumber, 123)
  374
+    @mock.patch('lib.marketplace.api.client.slumber')
  375
+    def test_marketplace_seller_switch(self, marketplace, solitude):
  376
+        marketplace.webpay.prices.return_value = self.prices
  377
+        self.set_billing_id(solitude, 123)
353 378
 
354 379
         # Simulate how the Marketplace would add
355 380
         # a custom seller_uuid to the product data in the JWT.
@@ -361,7 +386,7 @@ def test_marketplace_seller_switch(self, slumber):
361 386
 
362 387
         self.start()
363 388
         # Check that the seller_uuid was switched to that of the app seller.
364  
-        slumber.generic.seller.get.assert_called_with(
  389
+        solitude.generic.seller.get.assert_called_with(
365 390
             uuid=app_seller_uuid)
366 391
 
367 392
     @raises(ValueError)
26  webpay/pay/tests/test_views.py
@@ -8,7 +8,7 @@
8 8
 
9 9
 import mock
10 10
 from nose import SkipTest
11  
-from nose.tools import eq_
  11
+from nose.tools import eq_, ok_
12 12
 
13 13
 from webpay.pay.forms import VerifyForm
14 14
 from webpay.pay.models import (Issuer, ISSUER_ACTIVE, ISSUER_INACTIVE,
@@ -76,16 +76,21 @@ def test_get(self):
76 76
         eq_(self.client.get(self.url).status_code, 400)
77 77
 
78 78
     @mock.patch('lib.solitude.api.SolitudeAPI.get_secret')
79  
-    def test_inapp(self, get_secret):
  79
+    @mock.patch('lib.marketplace.api.MarketplaceAPI.get_price')
  80
+    def test_inapp(self, get_price, get_secret):
80 81
         get_secret.return_value = self.secret
81 82
         payload = self.request(iss=self.key, app_secret=self.secret)
82 83
         eq_(self.get(payload).status_code, 200)
83 84
 
84  
-    def test_inapp_wrong_secret(self):
  85
+    @mock.patch('lib.solitude.api.SolitudeAPI.get_secret')
  86
+    def test_inapp_wrong_secret(self, get_secret):
  87
+        get_secret.return_value = self.secret
85 88
         payload = self.request(iss=self.key, app_secret=self.secret + '.nope')
86 89
         eq_(self.get(payload).status_code, 400)
87 90
 
88  
-    def test_inapp_wrong_key(self):
  91
+    @mock.patch('lib.solitude.api.SolitudeAPI.get_secret')
  92
+    def test_inapp_wrong_key(self, get_secret):
  93
+        get_secret.side_effect = ValueError
89 94
         payload = self.request(iss=self.key + '.nope', app_secret=self.secret)
90 95
         eq_(self.get(payload).status_code, 400)
91 96
 
@@ -95,24 +100,25 @@ def test_bad_payload(self):
95 100
     def test_unicode_payload(self):
96 101
         eq_(self.get(u'Հ').status_code, 400)
97 102
 
98  
-    def test_purchase(self):
  103
+    @mock.patch('lib.marketplace.api.MarketplaceAPI.get_price')
  104
+    def test_purchase(self, get_price):
99 105
         payload = self.request()
100 106
         eq_(self.get(payload).status_code, 200)
101 107
         trans = Transaction.objects.get()
102 108
         eq_(trans.state, TRANS_STATE_PENDING)
103 109
         eq_(trans.issuer, None)
104 110
         eq_(trans.issuer_key, settings.KEY)
105  
-        self.start_pay.delay.assert_called_with(trans.pk)
  111
+        ok_(self.start_pay.delay.call_args[0][0], trans.pk)
106 112
 
107  
-    def test_missing_price(self):
  113
+    def test_missing_tier(self):
108 114
         payjwt = self.payload()
109  
-        del payjwt['request']['price']
  115
+        del payjwt['request']['pricePoint']
110 116
         payload = self.request(payload=payjwt)
111 117
         eq_(self.get(payload).status_code, 400)
112 118
 
113  
-    def test_empty_price(self):
  119
+    def test_empty_tier(self):
114 120
         payjwt = self.payload()
115  
-        payjwt['request']['price'] = []
  121
+        payjwt['request']['pricePoint'] = ''
116 122
         payload = self.request(payload=payjwt)
117 123
         eq_(self.get(payload).status_code, 400)
118 124
 
23  webpay/pay/views.py
... ...
@@ -1,4 +1,3 @@
1  
-from decimal import Decimal
2 1
 import json
3 2
 
4 3
 from django import http
@@ -15,6 +14,9 @@
15 14
 from webpay.auth.decorators import user_verified
16 15
 from webpay.base.decorators import json_view
17 16
 from webpay.pin.forms import VerifyPinForm
  17
+
  18
+from lib.marketplace.api import client, HttpClientError, TierNotFound
  19
+
18 20
 from . import tasks
19 21
 from .forms import VerifyForm
20 22
 from .models import (Issuer, Transaction, TRANS_STATE_PENDING,
@@ -47,14 +49,20 @@ def lobby(request):
47 49
             settings.DOMAIN,  # JWT audience.
48 50
             form.secret,
49 51
             required_keys=('request.id',
50  
-                           'request.price',  # An array of
51  
-                                             # price/currency
  52
+                           'request.pricePoint',  # A price tier we'll lookup.
52 53
                            'request.name',
53 54
                            'request.description'))
54 55
     except (TypeError, InvalidJWT, RequestExpired), exc:
55 56
         log.exception('calling verify_jwt')
56 57
         return _error(request, exception=exc)
57 58
 
  59
+    # Assert pricePoint is valid.
  60
+    try:
  61
+        client.get_price(pay_req['request']['pricePoint'])
  62
+    except (TierNotFound, HttpClientError), exc:
  63
+        log.exception('calling verifying tier')
  64
+        return _error(request, exception=exc)
  65
+
58 66
     try:
59 67
         iss = Issuer.objects.get(issuer_key=form.key)
60 68
     except Issuer.DoesNotExist:
@@ -70,18 +78,21 @@ def lobby(request):
70 78
        issuer=iss,
71 79
        issuer_key=form.key,
72 80
        product_id=pay_req['request']['id'],
73  
-       amount=Decimal(pay_req['request']['price'][0]['amount']),
74  
-       currency=pay_req['request']['price'][0]['currency'],
  81
+       amount=1,  # Set this temporarily until we remove transactions.
  82
+       currency='USD',  # This too.
75 83
        name=pay_req['request']['name'],
76 84
        description=desc,
77 85
        json_request=json.dumps(pay_req))
  86
+
78 87
     request.session['trans_id'] = trans.pk
  88
+    request.session['pay_request'] = pay_req
79 89
 
80 90
     # Before we verify the user's PIN let's save some
81 91
     # time and get the transaction configured via Bango in the
82 92
     # background.
83 93
     if not settings.FAKE_PAYMENTS:
84  
-        tasks.start_pay.delay(request.session['trans_id'])
  94
+        tasks.start_pay.delay(request.session['trans_id'],
  95
+                              request.session['pay_request'])
85 96
 
86 97
     return render(request, 'pay/lobby.html', {'pin_form': pin_form})
87 98
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.