Permalink
Browse files

Behold: the in-app payment tester app

  • Loading branch information...
1 parent bd3c52f commit 4a8f6e360244451cce6c081cc9741d2a764b65f4 @kumar303 committed Apr 6, 2012
View
@@ -17,3 +17,4 @@ build
tmp/*
*~
*.mo
+*.db
View
@@ -1,3 +1,6 @@
[submodule "vendor"]
path = vendor
url = git://github.com/mozilla/playdoh-lib.git
+[submodule "vendor-local/pyjwt"]
+ path = vendor-local/pyjwt
+ url = git://github.com/kumar303/pyjwt.git
View
@@ -1,17 +1,10 @@
-playdoh
-=======
+In-app Payment Tester
+=====================
-Mozilla's Playdoh is a web application template based on [Django][django].
-
-Patches are welcome! Feel free to fork and contribute to this project on
-[github][gh-playdoh].
-
-Full [documentation][docs] is available as well.
-
-
-[django]: http://www.djangoproject.com/
-[gh-playdoh]: https://github.com/mozilla/playdoh
-[docs]: http://playdoh.rtfd.org/
+ git clone --recursive
+ workon inapp_pay_test
+ pip install -r requirements/compiled.txt
+ python manage.py syncdb
License
@@ -0,0 +1,43 @@
+import functools
+import json
+
+from django import http
+
+import commonware
+
+log = commonware.log.getLogger(__name__)
+
+
+def post_required(view):
+ @functools.wraps(view)
+ def wrapper(request, *args, **kw):
+ if request.method != 'POST':
+ return http.HttpResponseNotAllowed(['POST'])
+ else:
+ return view(request, *args, **kw)
+ return wrapper
+
+
+def json_view(f=None, has_trans=False):
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kw):
+ try:
+ response = func(*args, **kw)
+ if isinstance(response, http.HttpResponse):
+ return response
+ else:
+ if has_trans:
+ response = json.dumps(response, cls=json.JSONEncoder)
+ else:
+ response = json.dumps(response)
+ return http.HttpResponse(response,
+ content_type='application/json')
+ except:
+ log.exception('Exception in @json_view')
+ raise
+ return wrapper
+ if f:
+ return decorator(f)
+ else:
+ return decorator
@@ -0,0 +1,27 @@
+from django.db import models
+
+from tower import ugettext_lazy as _lazy
+
+
+class ModelBase(models.Model):
+ created = models.DateTimeField(auto_now_add=True)
+ modified = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ abstract = True
+ get_latest_by = 'created'
+
+
+TRANS_PENDING = 1
+TRANS_DONE = 2
+TRANS_STATE_CHOICES = {TRANS_PENDING: _lazy('pending'),
+ TRANS_DONE: _lazy('done')}
+
+
+class Transaction(ModelBase):
+ moz_transaction_id = models.IntegerField(blank=True, null=True)
+ product = models.CharField(max_length=100)
+ currency = models.CharField(max_length=3)
+ price = models.DecimalField(max_digits=9, decimal_places=2)
+ description = models.CharField(max_length=255, blank=True)
+ state = models.IntegerField(choices=TRANS_STATE_CHOICES.items())
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset='utf-8'>
+
+ <title>{{ _('Mozilla In-app Payment Tester') }}</title>
+
+ {% block site_css %}
+ {{ css('app_payments') }}
+ {% endblock %}
+
+</head>
+<body>
+ <a id="fork-me" href="https://github.com/kumar303/inapp-pay-test">
+ <img src="{{ MEDIA_URL }}img/forkme_right_darkblue_121621.png" alt="{{ _('Fork me on GitHub') }}" />
+ </a>
+ <div id="container">
+ <section id="generator">
+ <h2>{{ _('Generate an in-app payment') }}
+ <a href="https://developer.mozilla.org/en/Apps/In-app_payments">({{ _('docs') }})</a></h2>
+ <a id="start-over" href="#">{{ _('Start over') }}</a>
+ <form>
+ {{ csrf() }}
+ <textarea name="pay_request" id="pay-request">{{ pay_request }}</textarea>
+ <p>{{ '<script type="text/javascript" src="' }}{{ settings.INAPP_PAYMENTS_JS }}{{ '"></script>' }}</p>
+ <div id="call-buy">
+ <button>{{ _('call mozmarket.buy()') }}</button>
+ </div>
+ </form>
+ </section>
+ <section id="log"><pre></pre></section>
+ </div>
+
+ {% block site_js %}
+ {{ js('app_payments') }}
+ {% endblock %}
+ <script type="text/javascript" src="{{ settings.INAPP_PAYMENTS_JS }}"></script>
+
+</body>
+</html>
@@ -0,0 +1,7 @@
+{"name": "In-app Payment Tester",
+ "description": "Utility app to test in-app payments.",
+ "version": "1.0",
+ "developer": {"name": "Kumar McMillan"},
+ "launch_path": "{{ url('app_payments.home') }}",
+ "default_locale": "en",
+ "installs_allowed_from": ["*"]}
@@ -0,0 +1,16 @@
+from django.conf.urls.defaults import *
+
+from . import views
+
+
+urlpatterns = patterns('',
+ url(r'^$', views.home, name='app_payments.home'),
+ url(r'^sign-request$', views.sign_request,
+ name='app_payments.sign_request'),
+ url(r'^manifest\.webapp$', views.manifest, name='app_payments.manifest'),
+ url(r'^check-trans$', views.check_trans, name='app.check_trans'),
+ url(r'^postback$', views.mozmarket_postback,
+ name='app.mozmarket_postback'),
+ url(r'^chargeback$', views.mozmarket_chargeback,
+ name='app.mozmarket_chargeback'),
+)
@@ -0,0 +1,120 @@
+import calendar
+import json
+import time
+import urlparse
+
+from django import http
+from django.conf import settings
+from django.shortcuts import render, get_object_or_404
+from django.views.decorators.csrf import csrf_exempt
+
+import commonware
+import jwt
+
+from .decorators import post_required, json_view
+from .models import Transaction, TRANS_PENDING, TRANS_DONE
+
+log = commonware.log.getLogger(__name__)
+
+
+def home(request):
+ iat = calendar.timegm(time.gmtime())
+ exp = iat + 3600 # expires in 1 hour
+ pay_request = json.dumps({
+ 'iss': settings.APPLICATION_KEY,
+ 'aud': 'marketplace.mozilla.org',
+ 'typ': 'mozilla/payments/pay/v1',
+ 'exp': exp,
+ 'iat': iat,
+ 'request': {
+ 'price': '0.99',
+ 'currency': 'USD',
+ 'name': 'The Product',
+ 'description': 'detailed description',
+ 'productdata': '<set to local transaction ID>'
+ }
+ }, indent=2)
+ return render(request, 'app_payments/home.html',
+ {'pay_request': pay_request})
+
+
+def manifest(request):
+ resp = render(request, 'app_payments/manifest.webapp', {})
+ resp['Content-Type'] = 'application/x-web-app-manifest+json'
+ return resp
+
+
+@post_required
+# TODO(Kumar) figure out why csrf() isn't working. Even without csrf
+# protection, users can still post arbitrary JSON from the form so csrf
+# is not a big deal.
+@csrf_exempt
+@json_view
+def sign_request(request):
+ #
+ # WARNING
+ #
+ # This is just a demo tool. You shouldn't ever sign a request
+ # generated entirely from user input.
+ #
+ try:
+ raw_pay_request = request.POST['pay_request']
+ pay_request = trans = None
+ try:
+ pay_request = json.loads(raw_pay_request)
+ trans = Transaction.objects.create(
+ state=TRANS_PENDING,
+ product=pay_request['request']['name'],
+ price=pay_request['request']['price'],
+ currency=pay_request['request']['currency'],
+ description=pay_request['request']['description'])
+ tx = 'transaction_id=%s' % trans.pk
+ pay_request['request']['productdata'] = tx
+ raw_pay_request = json.dumps(pay_request)
+ except (ValueError, KeyError):
+ log.exception('Invalid JSON, ignoring')
+
+ signed = jwt.encode(raw_pay_request,
+ settings.APPLICATION_SECRET, algorithm='HS256')
+ return {'localTransID': trans and trans.pk,
+ 'signedRequest': signed}
+ except:
+ log.exception('in sign_request()')
+ raise
+
+
+@post_required
+@csrf_exempt
+def mozmarket_postback(request):
+ try:
+ payload = request.read()
+ data = jwt.decode(payload, verify=False)
+ log.info('Got postback payload: %s' % data)
+ jwt.decode(payload, settings.APPLICATION_SECRET, verify=True)
+ moz_trans_id = data['response']['transactionID']
+
+ # e.g. transaction_id=1234
+ pd = urlparse.parse_qs(data['request']['productdata'])
+ trans = Transaction.objects.get(pk=pd['transaction_id'][0])
+ trans.moz_transaction_id = moz_trans_id
+ trans.state = TRANS_DONE
+ trans.save()
+
+ log.info('signature verified; responding with %s' % moz_trans_id)
+ return http.HttpResponse(str(moz_trans_id))
+ except:
+ log.exception('Exception while processing request from %s'
+ % request.META.get('REMOTE_ADDR'))
+ raise
+
+
+@csrf_exempt
+def mozmarket_chargeback(request):
+ pass
+
+
+@json_view
+def check_trans(request):
+ trans = get_object_or_404(Transaction, pk=request.GET.get('tx'))
+ return {'localTransID': trans.pk,
+ 'mozTransactionID': trans.moz_transaction_id}
@@ -3,6 +3,20 @@
from funfactory.settings_base import *
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': 'inapp_pay_test.db',
+ 'USER': '',
+ 'PASSWORD': '',
+ 'HOST': '',
+ 'PORT': '',
+ 'OPTIONS': {},
+ 'TEST_CHARSET': 'utf8',
+ 'TEST_COLLATION': 'utf8_general_ci',
+ },
+}
+
# Bundles is a dictionary of two dictionaries, css and js, which list css files
# and js files that can be bundled together by the minify app.
MINIFY_BUNDLES = {
@@ -13,13 +27,20 @@
'example_mobile_css': (
'css/examples/mobile.css',
),
+ 'app_payments': (
+ 'css/app_payments/base.css',
+ ),
},
'js': {
'example_js': (
'js/examples/libs/jquery-1.4.4.min.js',
'js/examples/libs/jquery.cookie.js',
'js/examples/init.js',
),
+ 'app_payments': (
+ 'js/libs/jquery-1.4.4.min.js',
+ 'js/app_payments.js',
+ ),
}
}
@@ -29,8 +50,9 @@
INSTALLED_APPS = list(INSTALLED_APPS) + [
# Application base, containing global templates.
'inapp_pay_test.base',
- # Example code. Can (and should) be removed for actual projects.
- 'inapp_pay_test.examples',
+ 'inapp_pay_test.app_payments',
+
+ 'django.contrib.admin',
]
@@ -56,4 +78,19 @@
# ('media/js/**.js', 'javascript'),
# ]
-LOGGING = dict(loggers=dict(playdoh = {'level': logging.DEBUG}))
+LOGGIN = {'loggers': {'playdoh': {'level': logging.DEBUG}}}
+
+# URL to the JS file for the app to include to make in-app payments.
+# By default this is the local reference implementation.
+INAPP_PAYMENTS_JS = 'https://marketplace-dev-cdn.allizom.org/media/js/mozmarket.js'
+
+# After registering an app for in-app payments
+# on https://marketplace.mozilla.org/
+# fill these in from the Manage In-app Payments screen.
+#
+# set these in settings/local.py
+#
+# **DO NOT** commit your app secret to github :)
+#
+APPLICATION_KEY = '<from marketplace.mozilla.org>'
+APPLICATION_SECRET = '<from marketplace.mozilla.org>'
Oops, something went wrong. Retry.

0 comments on commit 4a8f6e3

Please sign in to comment.