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

Commit

Permalink
Merge pull request #42 from andymckay/826474
Browse files Browse the repository at this point in the history
remove issuer, reformat, add in curling and require URLs in JSON -a (bug...
  • Loading branch information
Andy McKay committed Jan 17, 2013
2 parents 53d37d3 + d0b5046 commit ca6e767
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 175 deletions.
3 changes: 1 addition & 2 deletions lib/utils.py
@@ -1,7 +1,6 @@
import json

#from curling.lib import API
from slumber import API
from curling.lib import API
from slumber.exceptions import HttpClientError


Expand Down
2 changes: 1 addition & 1 deletion requirements/prod.txt
Expand Up @@ -5,7 +5,7 @@ anyjson==0.3.3
billiard==2.7.3.17
cef==0.3
celery==2.5.1
curling==0.1.4
curling==0.1.6
dj-database-url==0.2.1
django-celery==2.2.4
django-mobility==0.1
Expand Down
4 changes: 3 additions & 1 deletion webpay/bango/tests/test_views.py
Expand Up @@ -38,7 +38,9 @@ def test_good_return(self, payment_notify, slumber):
payment_notify.delay.assert_called_with(self.trans_uuid)

def test_invalid_return(self, payment_notify, slumber):
slumber.bango.notification.post.side_effect = HttpClientError
err = HttpClientError
err.content = ''
slumber.bango.notification.post.side_effect = err
self.call(expected_status=400)
assert not payment_notify.delay.called

Expand Down
10 changes: 10 additions & 0 deletions webpay/constants.py
@@ -0,0 +1,10 @@
from lib.solitude import constants


TYP_POSTBACK = 'mozilla/payments/pay/postback/v1'
TYP_CHARGEBACK = 'mozilla/payments/pay/chargeback/v1'

TYPE_STRINGS = {
constants.TYPE_PAYMENT: TYP_POSTBACK,
constants.TYPE_REFUND: TYP_CHARGEBACK
}
4 changes: 3 additions & 1 deletion webpay/pay/samples.py
Expand Up @@ -31,7 +31,9 @@ def payload(self, iss=None, aud=None, exp=None, iat=None,
'id': 'some-generated-unique-id',
'name': 'My bands latest album',
'description': '320kbps MP3 download, DRM free!',
'productdata': 'my_product_id=1234'
'productdata': 'my_product_id=1234',
'postbackURL': 'http://foo.url/post',
'chargebackURL': 'http://foo.url/charge'
}
if extra_req:
req.update(extra_req)
Expand Down
131 changes: 58 additions & 73 deletions webpay/pay/tasks.py
Expand Up @@ -17,7 +17,8 @@
from multidb.pinning import use_master

from webpay.base.helpers import absolutify
from .models import Issuer, Notice
from webpay.constants import TYP_CHARGEBACK, TYP_POSTBACK
from .models import Notice
from .utils import send_pay_notice

log = logging.getLogger('w.pay.tasks')
Expand All @@ -29,10 +30,45 @@ class TransactionOutOfSync(Exception):
"""The transaction's state is unexpected."""


def get_effective_issuer_key(issuer, issuer_key):
if issuer and issuer.issuer_key:
return issuer.issuer_key
return issuer_key
def get_secret(issuer_key):
"""Resolve the secret for this JWT."""
if is_marketplace(issuer_key):
return settings.SECRET
else:
return (client.slumber.generic.product
.get_object_or_404(external_id=issuer_key))['secret']


def get_seller_uuid(issuer_key, product_data):
"""Resolve the JWT into a seller uuid."""
if is_marketplace(issuer_key):
# The issuer of the JWT is Firefox Marketplace.
# This is a special case where we need to find the
# actual Solitude/Bango seller_uuid to associate the
# product to the right account.
try:
seller_uuid = urlparse.parse_qs(product_data)['seller_uuid'][0]
except KeyError:
raise ValueError('Marketplace %r did not put a seller_uuid '
'in productData: %r'
% (settings.KEY, product_data))
log.info('Using real seller_uuid %r for Marketplace %r '
'app payment' % (seller_uuid, settings.KEY))
return seller_uuid

else:
# The issuer of the JWT is the seller.
# Resolve this into the seller uuid.
#
# TODO: we can speed this up by having product return the full data.
product = (client.slumber.generic.product
.get_object_or_404(external_id=issuer_key))
return (client.slumber.generic.seller(product['seller'].split('/')[-2])
.get_object_or_404())['uuid']


def is_marketplace(issuer_key):
return issuer_key == settings.KEY


@task
Expand All @@ -48,33 +84,18 @@ def start_pay(transaction_uuid, notes, **kw):
# Because this is called from views, we get a new transaction every
# time. If you re-use this task, you'd want to add some checking about the
# transaction state.
pay_request = notes['pay_request']
iss = Issuer.objects.get(pk=notes['issuer']) if notes['issuer'] else None
pay = notes['pay_request']
try:
seller_uuid = get_effective_issuer_key(iss, notes['issuer_key'])
if seller_uuid == settings.KEY:
# The issuer of the JWT is Firefox Marketplace.
# This is a special case where we need to find the
# actual Solitude/Bango seller_uuid to associate the
# product to the right account.
prod_data = pay_request['request'].get('productData', '')
try:
seller_uuid = urlparse.parse_qs(prod_data)['seller_uuid'][0]
except KeyError:
raise ValueError('Marketplace %r did not put a seller_uuid '
'in productData: %r' % (settings.KEY,
prod_data))
log.info('Using real seller_uuid %r for Marketplace %r '
'app payment' % (seller_uuid, settings.KEY))

seller_uuid = get_seller_uuid(notes['issuer_key'],
pay['request'].get('productData', ''))
# Ask the marketplace for a valid price point.
prices = mkt_client.get_price(pay_request['request']['pricePoint'])
prices = mkt_client.get_price(pay['request']['pricePoint'])
# Set up the product for sale.
bill_id, seller_product = client.configure_product_for_billing(
transaction_uuid,
seller_uuid,
pay_request['request']['id'],
pay_request['request']['name'], # app/product name
pay['request']['id'],
pay['request']['name'], # app/product name
absolutify(reverse('bango.success')),
absolutify(reverse('bango.error')),
prices['prices']
Expand Down Expand Up @@ -131,38 +152,24 @@ def _notify(notifier_task, trans, extra_response=None):
Post JWT notice to an app server about a payment.
"""
# TODO(Kumar) yell if transaction is not completed?
#
# Hmm not sure what to do here.
if trans['notes']['issuer']:
(private_key, url) = _prepare_inapp_notice(trans,
trans['notes']['issuer'])
else:
(private_key, url) = _prepare_mkt_notice(trans,
trans['notes']['issuer'],
trans['notes']['pay_request'])

if trans['type'] == constants.TYPE_PAYMENT:
typ = 'mozilla/payments/pay/postback/v1'
elif trans['type'] == constants.TYPE_REFUND:
typ = 'mozilla/payments/pay/chargeback/v1'
else:
raise NotImplementedError('Unknown type: %s' % trans['type'])

typ, url = _prepare_notice(trans)
response = {'transactionID': trans['uuid']}
notes = trans['notes']

if extra_response:
response.update(extra_response)

issued_at = calendar.timegm(time.gmtime())
notice = {'iss': settings.NOTIFY_ISSUER,
'aud': trans['notes']['issuer_key'], # ...
'aud': notes['issuer_key'],
'typ': typ,
'iat': issued_at,
'exp': issued_at + 3600, # Expires in 1 hour
'request': trans['notes']['pay_request']['request'],
'request': notes['pay_request']['request'],
'response': response}
log.info('preparing notice %s' % notice)
signed_notice = jwt.encode(notice,
private_key,

signed_notice = jwt.encode(notice, get_secret(notes['issuer_key']),
algorithm='HS256')
success, last_error = send_pay_notice(url, trans['type'], signed_notice,
trans['uuid'], notifier_task)
Expand All @@ -175,33 +182,11 @@ def _notify(notifier_task, trans, extra_response=None):
last_error=last_error)


def _prepare_inapp_notice(trans, issuer):
if trans['type'] == constants.TYPE_PAYMENT:
uri = issuer.postback_url
elif trans['type'] == constants.TYPE_REFUND:
uri = issuer.chargeback_url
else:
raise NotImplementedError('Unknown type: %s' % trans['type'])
url = urlparse.urlunparse((issuer.app_protocol(),
issuer.domain, uri, '',
'', ''))
return (issuer.get_private_key(), url)


def _prepare_mkt_notice(trans, issuer, pay_request):
if trans['notes']['issuer_key'] != settings.KEY:
raise ValueError('key %r is not allowed to make app purchases'
% trans['notes']['issuer_key'])
def _prepare_notice(trans):
request = trans['notes']['pay_request']['request']
if trans['type'] == constants.TYPE_PAYMENT:
if 'postbackURL' in pay_request['request']:
url = pay_request['request']['postbackURL']
else:
url = settings.MKT_POSTBACK
return TYP_POSTBACK, request['postbackURL']
elif trans['type'] == constants.TYPE_REFUND:
if 'chargebackURL' in pay_request['request']:
url = pay_request['request']['chargebackURL']
else:
url = settings.MKT_CHARGEBACK
return TYP_CHARGEBACK, request['chargebackURL']
else:
raise NotImplementedError('Unknown type: %s' % trans['type'])
return settings.SECRET, url

1 comment on commit ca6e767

@kumar303
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also remove INAPP_REQUIRE_HTTPS from base settings and also the dev/prod/stage base settings?

Please sign in to comment.