Skip to content
Browse files

require a tier, not prices (bug 821511)

  • Loading branch information...
1 parent 41096a7 commit 0f2e5820ba6074ecf92841272b83b4abf4ebc8cb @andymckay andymckay committed
View
14 lib/marketplace/api.py
@@ -1,13 +1,23 @@
from django.conf import settings
from ..utils import SlumberWrapper
+from slumber.exceptions import HttpClientError
+
+
+class TierNotFound(Exception):
+ pass
class MarketplaceAPI(SlumberWrapper):
errors = {}
def get_price(self, tier):
- return self.slumber.api.webpay.prices(id=tier).get()
-
+ # TODO: cache this.
+ try:
+ return self.slumber.api.webpay.prices(id=tier).get()
+ except HttpClientError, err:
+ if err.response.status_code:
+ raise TierNotFound(tier)
+ raise
client = MarketplaceAPI(settings.MARKETPLACE_URL or 'http://example.com')
View
11 lib/solitude/api.py
@@ -125,9 +125,9 @@ def verify_pin(self, uuid, pin):
def configure_product_for_billing(self, webpay_trans_id,
seller_uuid,
product_id, product_name,
- currency, amount,
redirect_url_onsuccess,
- redirect_url_onerror):
+ redirect_url_onerror,
+ prices):
"""
Get the billing configuration ID for a Bango transaction.
"""
@@ -145,7 +145,9 @@ def configure_product_for_billing(self, webpay_trans_id,
)
if res['meta']['total_count'] == 0:
bango_product_uri = self.create_product(product_id,
- product_name, currency, amount, res['objects'][0])
+ # TODO: look at why we need currency and price for the
+ # premium call. This might be a Bango issue.
+ product_name, 1, 'EUR', res['objects'][0])
else:
bango_product_uri = res['objects'][0]['resource_uri']
log.info('transaction %s: bango product: %s'
@@ -153,8 +155,7 @@ def configure_product_for_billing(self, webpay_trans_id,
res = self.slumber.bango.billing.post({
'pageTitle': product_name,
- 'price_currency': currency,
- 'price_amount': str(amount),
+ 'prices': prices,
'seller_product_bango': bango_product_uri,
'redirect_url_onsuccess': redirect_url_onsuccess,
'redirect_url_onerror': redirect_url_onerror,
View
5 webpay/pay/samples.py
@@ -27,10 +27,7 @@ def payload(self, iss=None, aud=None, exp=None, iat=None,
exp = iat + 3600 # Expires in 1 hour.
req = {
- 'price': [{
- 'amount': '0.99',
- 'currency': 'USD',
- }],
+ 'pricePoint': 1,
'id': 'some-generated-unique-id',
'name': 'My bands latest album',
'description': '320kbps MP3 download, DRM free!',
View
7 webpay/pay/tasks.py
@@ -11,6 +11,7 @@
from celeryutils import task
import jwt
+from lib.marketplace.api import client as mkt_client
from lib.solitude.api import client
from multidb.pinning import use_master
@@ -32,7 +33,7 @@ class TransactionOutOfSync(Exception):
@task
@use_master
@transaction.commit_on_success
-def start_pay(trans_id, **kw):
+def start_pay(trans_id, pay_request, **kw):
"""
Work with Solitude to begin a Bango payment.
@@ -62,15 +63,15 @@ def start_pay(trans_id, **kw):
log.info('Using real seller_uuid %r for Marketplace %r '
'app payment' % (seller_uuid, settings.KEY))
+ prices = mkt_client.get_price(pay_request['request']['pricePoint'])
bill_id = client.configure_product_for_billing(
trans.pk,
seller_uuid,
trans.product_id,
trans.name, # app/product name
- trans.currency,
- trans.amount,
absolutify(reverse('bango.success')),
absolutify(reverse('bango.error')),
+ prices['prices']
)
trans.bango_config_id = bill_id
trans.state = TRANS_STATE_READY
View
59 webpay/pay/tests/test_tasks.py
@@ -10,6 +10,7 @@
import fudge
from fudge.inspector import arg
import jwt
+from lib.marketplace.api import TierNotFound
from lib.solitude import api
import mock
from nose.tools import eq_, raises
@@ -50,8 +51,8 @@ def setUp(self):
state=TRANS_STATE_COMPLETED,
issuer=self.iss,
issuer_key=self.iss.issuer_key,
- amount=Decimal(app_payment['request']['price'][0]['amount']),
- currency=app_payment['request']['price'][0]['currency'],
+ amount=1, # Temporary until we get rid of transactions.
+ currency='USD', # This too.
name=app_payment['request']['name'],
description=app_payment['request']['description'],
json_request=json.dumps(app_payment))
@@ -233,10 +234,7 @@ def is_valid(payload):
eq_(data['iss'], settings.NOTIFY_ISSUER)
eq_(data['aud'], self.iss.issuer_key)
eq_(data['typ'], 'mozilla/payments/pay/postback/v1')
- eq_(data['request']['price'][0]['amount'],
- app_payment['request']['price'][0]['amount'])
- eq_(data['request']['price'][0]['currency'],
- app_payment['request']['price'][0]['currency'])
+ eq_(data['request']['pricePoint'], 1)
eq_(data['request']['name'], app_payment['request']['name'])
eq_(data['request']['description'],
app_payment['request']['description'])
@@ -272,12 +270,14 @@ def setUp(self):
description='A sword to use in Awesome Castle Game',
json_request='{}',
)
+ self.payload = {'request': {'pricePoint': 1}}
+ self.prices = {'prices': {'amount': 1, 'currency': 'EUR'}}
def get_trans(self):
return Transaction.objects.get(pk=self.trans.pk)
def start(self):
- tasks.start_pay(self.trans.pk)
+ tasks.start_pay(self.trans.pk, self.payload)
def update(self, **kw):
Transaction.objects.filter(pk=self.trans.pk).update(**kw)
@@ -304,14 +304,18 @@ def test_already_failed(self):
@raises(api.SellerNotConfigured)
@mock.patch('lib.solitude.api.client.slumber')
- def test_no_seller(self, slumber):
- slumber.generic.seller.get.return_value = {'meta': {'total_count': 0}}
+ @mock.patch('lib.marketplace.api.client.slumber')
+ def test_no_seller(self, marketplace, solitude):
+ marketplace.webpay.prices.return_value = self.prices
+ solitude.generic.seller.get.return_value = {'meta': {'total_count': 0}}
self.start()
eq_(self.get_trans().state, TRANS_STATE_FAILED)
@mock.patch('lib.solitude.api.client.slumber')
- def test_existing_product(self, slumber):
- slumber.generic.seller.get.return_value = {
+ @mock.patch('lib.marketplace.api.client.slumber')
+ def test_existing_product(self, marketplace, solitude):
+ marketplace.webpay.prices.return_value = self.prices
+ solitude.generic.seller.get.return_value = {
'meta': {'total_count': 1},
'objects': [{
'resource_pk': 29,
@@ -319,7 +323,7 @@ def test_existing_product(self, slumber):
'resource_uri': '/generic/seller/29/'
}]
}
- slumber.bango.product.get.return_value = {
+ solitude.bango.product.get.return_value = {
'meta': {'total_count': 1},
'objects': [{
'resource_pk': 15,
@@ -332,15 +336,34 @@ def test_existing_product(self, slumber):
'/bango/product/15/'
}]
}
- self.set_billing_id(slumber, 123)
+ self.set_billing_id(solitude, 123)
self.start()
trans = self.get_trans()
eq_(trans.state, TRANS_STATE_READY)
eq_(trans.bango_config_id, 123)
@mock.patch('lib.solitude.api.client.slumber')
+ @mock.patch('lib.marketplace.api.client.slumber')
+ def test_price_used(self, marketplace, solitude):
+ prices = mock.Mock()
+ prices.get.return_value = self.prices
+ marketplace.api.webpay.prices.return_value = prices
+ self.set_billing_id(solitude, 123)
+ self.start()
+ eq_(solitude.bango.billing.post.call_args[0][0]['prices'],
+ self.prices['prices'])
+
+ @mock.patch('lib.solitude.api.client.slumber')
+ @mock.patch('lib.marketplace.api.client.slumber')
+ def test_price_fails(self, marketplace, solitude):
+ marketplace.api.webpay.prices.side_effect = TierNotFound
+ with self.assertRaises(TierNotFound):
+ self.start()
+
+ @mock.patch('lib.solitude.api.client.slumber')
+ @mock.patch('lib.marketplace.api.client.slumber')
@raises(RuntimeError)
- def test_exception_fails_transaction(self, slumber):
+ def test_exception_fails_transaction(self, marketplace, slumber):
slumber.generic.seller.get.side_effect = RuntimeError
self.start()
trans = self.get_trans()
@@ -348,8 +371,10 @@ def test_exception_fails_transaction(self, slumber):
@mock.patch.object(settings, 'KEY', 'marketplace-domain')
@mock.patch('lib.solitude.api.client.slumber')
- def test_marketplace_seller_switch(self, slumber):
- self.set_billing_id(slumber, 123)
+ @mock.patch('lib.marketplace.api.client.slumber')
+ def test_marketplace_seller_switch(self, marketplace, solitude):
+ marketplace.webpay.prices.return_value = self.prices
+ self.set_billing_id(solitude, 123)
# Simulate how the Marketplace would add
# a custom seller_uuid to the product data in the JWT.
@@ -361,7 +386,7 @@ def test_marketplace_seller_switch(self, slumber):
self.start()
# Check that the seller_uuid was switched to that of the app seller.
- slumber.generic.seller.get.assert_called_with(
+ solitude.generic.seller.get.assert_called_with(
uuid=app_seller_uuid)
@raises(ValueError)
View
26 webpay/pay/tests/test_views.py
@@ -8,7 +8,7 @@
import mock
from nose import SkipTest
-from nose.tools import eq_
+from nose.tools import eq_, ok_
from webpay.pay.forms import VerifyForm
from webpay.pay.models import (Issuer, ISSUER_ACTIVE, ISSUER_INACTIVE,
@@ -76,16 +76,21 @@ def test_get(self):
eq_(self.client.get(self.url).status_code, 400)
@mock.patch('lib.solitude.api.SolitudeAPI.get_secret')
- def test_inapp(self, get_secret):
+ @mock.patch('lib.marketplace.api.MarketplaceAPI.get_price')
+ def test_inapp(self, get_price, get_secret):
get_secret.return_value = self.secret
payload = self.request(iss=self.key, app_secret=self.secret)
eq_(self.get(payload).status_code, 200)
- def test_inapp_wrong_secret(self):
+ @mock.patch('lib.solitude.api.SolitudeAPI.get_secret')
+ def test_inapp_wrong_secret(self, get_secret):
+ get_secret.return_value = self.secret
payload = self.request(iss=self.key, app_secret=self.secret + '.nope')
eq_(self.get(payload).status_code, 400)
- def test_inapp_wrong_key(self):
+ @mock.patch('lib.solitude.api.SolitudeAPI.get_secret')
+ def test_inapp_wrong_key(self, get_secret):
+ get_secret.side_effect = ValueError
payload = self.request(iss=self.key + '.nope', app_secret=self.secret)
eq_(self.get(payload).status_code, 400)
@@ -95,24 +100,25 @@ def test_bad_payload(self):
def test_unicode_payload(self):
eq_(self.get(u'Հ').status_code, 400)
- def test_purchase(self):
+ @mock.patch('lib.marketplace.api.MarketplaceAPI.get_price')
+ def test_purchase(self, get_price):
payload = self.request()
eq_(self.get(payload).status_code, 200)
trans = Transaction.objects.get()
eq_(trans.state, TRANS_STATE_PENDING)
eq_(trans.issuer, None)
eq_(trans.issuer_key, settings.KEY)
- self.start_pay.delay.assert_called_with(trans.pk)
+ ok_(self.start_pay.delay.call_args[0][0], trans.pk)
- def test_missing_price(self):
+ def test_missing_tier(self):
payjwt = self.payload()
- del payjwt['request']['price']
+ del payjwt['request']['pricePoint']
payload = self.request(payload=payjwt)
eq_(self.get(payload).status_code, 400)
- def test_empty_price(self):
+ def test_empty_tier(self):
payjwt = self.payload()
- payjwt['request']['price'] = []
+ payjwt['request']['pricePoint'] = ''
payload = self.request(payload=payjwt)
eq_(self.get(payload).status_code, 400)
View
23 webpay/pay/views.py
@@ -1,4 +1,3 @@
-from decimal import Decimal
import json
from django import http
@@ -15,6 +14,9 @@
from webpay.auth.decorators import user_verified
from webpay.base.decorators import json_view
from webpay.pin.forms import VerifyPinForm
+
+from lib.marketplace.api import client, HttpClientError, TierNotFound
+
from . import tasks
from .forms import VerifyForm
from .models import (Issuer, Transaction, TRANS_STATE_PENDING,
@@ -47,14 +49,20 @@ def lobby(request):
settings.DOMAIN, # JWT audience.
form.secret,
required_keys=('request.id',
- 'request.price', # An array of
- # price/currency
+ 'request.pricePoint', # A price tier we'll lookup.
'request.name',
'request.description'))
except (TypeError, InvalidJWT, RequestExpired), exc:
log.exception('calling verify_jwt')
return _error(request, exception=exc)
+ # Assert pricePoint is valid.
+ try:
+ client.get_price(pay_req['request']['pricePoint'])
+ except (TierNotFound, HttpClientError), exc:
+ log.exception('calling verifying tier')
+ return _error(request, exception=exc)
+
try:
iss = Issuer.objects.get(issuer_key=form.key)
except Issuer.DoesNotExist:
@@ -70,18 +78,21 @@ def lobby(request):
issuer=iss,
issuer_key=form.key,
product_id=pay_req['request']['id'],
- amount=Decimal(pay_req['request']['price'][0]['amount']),
- currency=pay_req['request']['price'][0]['currency'],
+ amount=1, # Set this temporarily until we remove transactions.
+ currency='USD', # This too.
name=pay_req['request']['name'],
description=desc,
json_request=json.dumps(pay_req))
+
request.session['trans_id'] = trans.pk
+ request.session['pay_request'] = pay_req
# Before we verify the user's PIN let's save some
# time and get the transaction configured via Bango in the
# background.
if not settings.FAKE_PAYMENTS:
- tasks.start_pay.delay(request.session['trans_id'])
+ tasks.start_pay.delay(request.session['trans_id'],
+ request.session['pay_request'])
return render(request, 'pay/lobby.html', {'pin_form': pin_form})

0 comments on commit 0f2e582

Please sign in to comment.
Something went wrong with that request. Please try again.