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

Commit 0f2e582

Browse files
author
Andy McKay
committed
require a tier, not prices (bug 821511)
1 parent 41096a7 commit 0f2e582

File tree

7 files changed

+98
-47
lines changed

7 files changed

+98
-47
lines changed

lib/marketplace/api.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
from django.conf import settings
22

33
from ..utils import SlumberWrapper
4+
from slumber.exceptions import HttpClientError
5+
6+
7+
class TierNotFound(Exception):
8+
pass
49

510

611
class MarketplaceAPI(SlumberWrapper):
712
errors = {}
813

914
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
1222

1323
client = MarketplaceAPI(settings.MARKETPLACE_URL or 'http://example.com')

lib/solitude/api.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ def verify_pin(self, uuid, pin):
125125
def configure_product_for_billing(self, webpay_trans_id,
126126
seller_uuid,
127127
product_id, product_name,
128-
currency, amount,
129128
redirect_url_onsuccess,
130-
redirect_url_onerror):
129+
redirect_url_onerror,
130+
prices):
131131
"""
132132
Get the billing configuration ID for a Bango transaction.
133133
"""
@@ -145,16 +145,17 @@ def configure_product_for_billing(self, webpay_trans_id,
145145
)
146146
if res['meta']['total_count'] == 0:
147147
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])
149151
else:
150152
bango_product_uri = res['objects'][0]['resource_uri']
151153
log.info('transaction %s: bango product: %s'
152154
% (webpay_trans_id, bango_product_uri))
153155

154156
res = self.slumber.bango.billing.post({
155157
'pageTitle': product_name,
156-
'price_currency': currency,
157-
'price_amount': str(amount),
158+
'prices': prices,
158159
'seller_product_bango': bango_product_uri,
159160
'redirect_url_onsuccess': redirect_url_onsuccess,
160161
'redirect_url_onerror': redirect_url_onerror,

webpay/pay/samples.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@ def payload(self, iss=None, aud=None, exp=None, iat=None,
2727
exp = iat + 3600 # Expires in 1 hour.
2828

2929
req = {
30-
'price': [{
31-
'amount': '0.99',
32-
'currency': 'USD',
33-
}],
30+
'pricePoint': 1,
3431
'id': 'some-generated-unique-id',
3532
'name': 'My bands latest album',
3633
'description': '320kbps MP3 download, DRM free!',

webpay/pay/tasks.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from celeryutils import task
1313
import jwt
14+
from lib.marketplace.api import client as mkt_client
1415
from lib.solitude.api import client
1516
from multidb.pinning import use_master
1617

@@ -32,7 +33,7 @@ class TransactionOutOfSync(Exception):
3233
@task
3334
@use_master
3435
@transaction.commit_on_success
35-
def start_pay(trans_id, **kw):
36+
def start_pay(trans_id, pay_request, **kw):
3637
"""
3738
Work with Solitude to begin a Bango payment.
3839
@@ -62,15 +63,15 @@ def start_pay(trans_id, **kw):
6263
log.info('Using real seller_uuid %r for Marketplace %r '
6364
'app payment' % (seller_uuid, settings.KEY))
6465

66+
prices = mkt_client.get_price(pay_request['request']['pricePoint'])
6567
bill_id = client.configure_product_for_billing(
6668
trans.pk,
6769
seller_uuid,
6870
trans.product_id,
6971
trans.name, # app/product name
70-
trans.currency,
71-
trans.amount,
7272
absolutify(reverse('bango.success')),
7373
absolutify(reverse('bango.error')),
74+
prices['prices']
7475
)
7576
trans.bango_config_id = bill_id
7677
trans.state = TRANS_STATE_READY

webpay/pay/tests/test_tasks.py

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import fudge
1111
from fudge.inspector import arg
1212
import jwt
13+
from lib.marketplace.api import TierNotFound
1314
from lib.solitude import api
1415
import mock
1516
from nose.tools import eq_, raises
@@ -50,8 +51,8 @@ def setUp(self):
5051
state=TRANS_STATE_COMPLETED,
5152
issuer=self.iss,
5253
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.
5556
name=app_payment['request']['name'],
5657
description=app_payment['request']['description'],
5758
json_request=json.dumps(app_payment))
@@ -233,10 +234,7 @@ def is_valid(payload):
233234
eq_(data['iss'], settings.NOTIFY_ISSUER)
234235
eq_(data['aud'], self.iss.issuer_key)
235236
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)
240238
eq_(data['request']['name'], app_payment['request']['name'])
241239
eq_(data['request']['description'],
242240
app_payment['request']['description'])
@@ -272,12 +270,14 @@ def setUp(self):
272270
description='A sword to use in Awesome Castle Game',
273271
json_request='{}',
274272
)
273+
self.payload = {'request': {'pricePoint': 1}}
274+
self.prices = {'prices': {'amount': 1, 'currency': 'EUR'}}
275275

276276
def get_trans(self):
277277
return Transaction.objects.get(pk=self.trans.pk)
278278

279279
def start(self):
280-
tasks.start_pay(self.trans.pk)
280+
tasks.start_pay(self.trans.pk, self.payload)
281281

282282
def update(self, **kw):
283283
Transaction.objects.filter(pk=self.trans.pk).update(**kw)
@@ -304,22 +304,26 @@ def test_already_failed(self):
304304

305305
@raises(api.SellerNotConfigured)
306306
@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}}
309311
self.start()
310312
eq_(self.get_trans().state, TRANS_STATE_FAILED)
311313

312314
@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 = {
315319
'meta': {'total_count': 1},
316320
'objects': [{
317321
'resource_pk': 29,
318322
'uuid': self.trans.issuer_key, # e.g. the app-for-sale domain
319323
'resource_uri': '/generic/seller/29/'
320324
}]
321325
}
322-
slumber.bango.product.get.return_value = {
326+
solitude.bango.product.get.return_value = {
323327
'meta': {'total_count': 1},
324328
'objects': [{
325329
'resource_pk': 15,
@@ -332,24 +336,45 @@ def test_existing_product(self, slumber):
332336
'/bango/product/15/'
333337
}]
334338
}
335-
self.set_billing_id(slumber, 123)
339+
self.set_billing_id(solitude, 123)
336340
self.start()
337341
trans = self.get_trans()
338342
eq_(trans.state, TRANS_STATE_READY)
339343
eq_(trans.bango_config_id, 123)
340344

341345
@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')
342365
@raises(RuntimeError)
343-
def test_exception_fails_transaction(self, slumber):
366+
def test_exception_fails_transaction(self, marketplace, slumber):
344367
slumber.generic.seller.get.side_effect = RuntimeError
345368
self.start()
346369
trans = self.get_trans()
347370
eq_(trans.state, TRANS_STATE_FAILED)
348371

349372
@mock.patch.object(settings, 'KEY', 'marketplace-domain')
350373
@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)
353378

354379
# Simulate how the Marketplace would add
355380
# a custom seller_uuid to the product data in the JWT.
@@ -361,7 +386,7 @@ def test_marketplace_seller_switch(self, slumber):
361386

362387
self.start()
363388
# 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(
365390
uuid=app_seller_uuid)
366391

367392
@raises(ValueError)

webpay/pay/tests/test_views.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import mock
1010
from nose import SkipTest
11-
from nose.tools import eq_
11+
from nose.tools import eq_, ok_
1212

1313
from webpay.pay.forms import VerifyForm
1414
from webpay.pay.models import (Issuer, ISSUER_ACTIVE, ISSUER_INACTIVE,
@@ -76,16 +76,21 @@ def test_get(self):
7676
eq_(self.client.get(self.url).status_code, 400)
7777

7878
@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):
8081
get_secret.return_value = self.secret
8182
payload = self.request(iss=self.key, app_secret=self.secret)
8283
eq_(self.get(payload).status_code, 200)
8384

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
8588
payload = self.request(iss=self.key, app_secret=self.secret + '.nope')
8689
eq_(self.get(payload).status_code, 400)
8790

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
8994
payload = self.request(iss=self.key + '.nope', app_secret=self.secret)
9095
eq_(self.get(payload).status_code, 400)
9196

@@ -95,24 +100,25 @@ def test_bad_payload(self):
95100
def test_unicode_payload(self):
96101
eq_(self.get(u'Հ').status_code, 400)
97102

98-
def test_purchase(self):
103+
@mock.patch('lib.marketplace.api.MarketplaceAPI.get_price')
104+
def test_purchase(self, get_price):
99105
payload = self.request()
100106
eq_(self.get(payload).status_code, 200)
101107
trans = Transaction.objects.get()
102108
eq_(trans.state, TRANS_STATE_PENDING)
103109
eq_(trans.issuer, None)
104110
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)
106112

107-
def test_missing_price(self):
113+
def test_missing_tier(self):
108114
payjwt = self.payload()
109-
del payjwt['request']['price']
115+
del payjwt['request']['pricePoint']
110116
payload = self.request(payload=payjwt)
111117
eq_(self.get(payload).status_code, 400)
112118

113-
def test_empty_price(self):
119+
def test_empty_tier(self):
114120
payjwt = self.payload()
115-
payjwt['request']['price'] = []
121+
payjwt['request']['pricePoint'] = ''
116122
payload = self.request(payload=payjwt)
117123
eq_(self.get(payload).status_code, 400)
118124

webpay/pay/views.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from decimal import Decimal
21
import json
32

43
from django import http
@@ -15,6 +14,9 @@
1514
from webpay.auth.decorators import user_verified
1615
from webpay.base.decorators import json_view
1716
from webpay.pin.forms import VerifyPinForm
17+
18+
from lib.marketplace.api import client, HttpClientError, TierNotFound
19+
1820
from . import tasks
1921
from .forms import VerifyForm
2022
from .models import (Issuer, Transaction, TRANS_STATE_PENDING,
@@ -47,14 +49,20 @@ def lobby(request):
4749
settings.DOMAIN, # JWT audience.
4850
form.secret,
4951
required_keys=('request.id',
50-
'request.price', # An array of
51-
# price/currency
52+
'request.pricePoint', # A price tier we'll lookup.
5253
'request.name',
5354
'request.description'))
5455
except (TypeError, InvalidJWT, RequestExpired), exc:
5556
log.exception('calling verify_jwt')
5657
return _error(request, exception=exc)
5758

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+
5866
try:
5967
iss = Issuer.objects.get(issuer_key=form.key)
6068
except Issuer.DoesNotExist:
@@ -70,18 +78,21 @@ def lobby(request):
7078
issuer=iss,
7179
issuer_key=form.key,
7280
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.
7583
name=pay_req['request']['name'],
7684
description=desc,
7785
json_request=json.dumps(pay_req))
86+
7887
request.session['trans_id'] = trans.pk
88+
request.session['pay_request'] = pay_req
7989

8090
# Before we verify the user's PIN let's save some
8191
# time and get the transaction configured via Bango in the
8292
# background.
8393
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'])
8596

8697
return render(request, 'pay/lobby.html', {'pin_form': pin_form})
8798

0 commit comments

Comments
 (0)