Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Closed
wants to merge 1 commit into from

2 participants

@andymckay
Owner

No description provided.

@kumar303 kumar303 commented on the diff
lib/marketplace/api.py
((9 lines not shown))
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
@kumar303 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,
exp = iat + 3600 # Expires in 1 hour.
req = {
- 'price': [{
- 'amount': '0.99',
- 'currency': 'USD',
- }],
+ 'tier': 1,
@kumar303 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))
'request.name',
'request.description'))
except (TypeError, InvalidJWT, RequestExpired), exc:
log.exception('calling verify_jwt')
return _error(request, exception=exc)
+ # Assert price tier is valid.
+ try:
+ client.get_price(pay_req['request']['tier'])
+ except (TierNotFound, HttpClientError), exc:
+ log.exception('calling verifying tier')
+ return _error(request, exception=exc)
+
@kumar303 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
@kumar303
Owner

r+wc

@andymckay andymckay closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 20, 2012
  1. @andymckay
This page is out of date. Refresh to see the latest.
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)
@kumar303 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
+ 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})
Something went wrong with that request. Please try again.