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

Commit

Permalink
Create in-app payment keys (bug 834486)
Browse files Browse the repository at this point in the history
  • Loading branch information
kumar303 committed Feb 11, 2013
1 parent ac87a25 commit 3e9334a
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 2 deletions.
6 changes: 5 additions & 1 deletion media/css/devreg/in-app-config.less
Expand Up @@ -2,11 +2,15 @@

.devhub-form .in-app-config {
input[type="text"] {
width: 37em;
width: 55em;
}
.is-https.hint {
display: inline;
}
.preamble {
padding-bottom: 2em;
line-height: 1.3em;
}
}

#in-app-private-key .secret {
Expand Down
14 changes: 14 additions & 0 deletions migrations/533-in-app-keys.sql
@@ -0,0 +1,14 @@
CREATE TABLE `user_inapp_keys` (
`id` int(11) unsigned AUTO_INCREMENT NOT NULL PRIMARY KEY,
`solitude_seller_id` int(11) unsigned NOT NULL,
`seller_product_pk` int(11) unsigned NOT NULL UNIQUE,
`created` datetime NOT NULL,
`modified` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `user_inapp_keys` ADD CONSTRAINT `solitude_seller_id_refs_id_cd630821`
FOREIGN KEY (`solitude_seller_id`) REFERENCES `payments_seller` (`id`);
CREATE INDEX `user_inapp_keys_613b0f94` ON `user_inapp_keys` (`solitude_seller_id`);

INSERT INTO waffle_switch_mkt (name, active, created, modified, note)
VALUES ('in-app-sandbox', 0, NOW(), NOW(),
'Enable the in-app payment sandbox');
32 changes: 32 additions & 0 deletions mkt/developers/models.py
Expand Up @@ -322,3 +322,35 @@ def delete(self):
pass

super(AddonPaymentAccount, self).delete()


class UserInappKey(CurlingHelper, amo.models.ModelBase):
solitude_seller = models.ForeignKey(SolitudeSeller)
seller_product_pk = models.IntegerField(unique=True)

def secret(self):
return self._product().get()['secret']

def public_id(self):
return self._product().get()['public_id']

def reset(self):
self._product().patch(data={'secret': generate_key(48)})

@classmethod
def create(cls, user):
sel = SolitudeSeller.create(user)
prod = client.post_product(data={
'seller': sel.resource_uri, 'secret': generate_key(48),
'external_id': str(uuid.uuid4()), 'public_id': str(uuid.uuid4())
})
log.info('User %s created an in-app payments dev key product=%s '
'with %s' % (user, prod['resource_pk'], sel))
return cls.objects.create(solitude_seller=sel,
seller_product_pk=prod['resource_pk'])

def _product(self):
return client.api.generic.product(self.seller_product_pk)

class Meta:
db_table = 'user_inapp_keys'
4 changes: 4 additions & 0 deletions mkt/developers/templates/developers/nav.html
Expand Up @@ -33,6 +33,10 @@
<a href="{{ url('mkt.developers.validate_addon') }}">
{{- _('App Validator') }}</a>
</li>
{% if waffle.switch('in-app-sandbox') %}
<li class="slim"><a href="{{ url('mkt.developers.apps.in_app_keys') }}">
{{ _('Payment Keys') }}</a></li>
{% endif %}
<li id="submit-app" class="slim">
<a href="{{ url('submit.app') }}">{{ _('Submit a New App') }}</a>
</li>
Expand Down
57 changes: 57 additions & 0 deletions mkt/developers/templates/developers/payments/in-app-keys.html
@@ -0,0 +1,57 @@
{% extends 'developers/base_impala.html' %}
{% from 'developers/includes/macros.html' import required %}

{% set title = _('In-App Payment Keys') %}
{% block title %}{{ hub_page_title(title, addon) }}{% endblock %}

{% block content %}
<header>
{{ hub_breadcrumbs(addon, items=[(None, title)]) }}
<h1>{{ title }}</h1>
</header>
{{ disabled_payments_notice() }}
<section class="island" role="main">
<div id="in-app-config" class="devhub-form">
<form class="item in-app-config" method="post" action="{{ request.path }}">
{{ csrf() }}
<p class="learn-mdn active"><a href="https://developer.mozilla.org/en-US/docs/Apps/In-app_payments" target="_blank">
{% trans %}Learn more about <b>in-app payments</b> on MDN.{% endtrans -%}
</a></p>
<p class="preamble">
{% trans submit=url('submit.app') %}
The following key/secret will only allow you to simulate in-app payments
while you develop your app. To generate a key for real in-app payments
you must <a href="{{ submit }}">submit</a> your app, set up your payment
credentials, and generate a new key specifically for that app.
{% endtrans %}
</p>
<table>
<tr id="in-app-public-key">
<th class="label">{{ _('Application Key') }}</th>
{% if key %}
<td><input type="text" value="{{ key.public_id() }}" readonly></td>
{% else %}
<td class="not-generated">({{ _('Not yet generated.') }})</td>
{% endif %}
</tr>
<tr id="in-app-private-key">
<th class="label">{{ _('Application Secret') }}</th>
{% if key %}
<td>
<button data-url="{{ url('mkt.developers.apps.in_app_key_secret',
key.pk) }}"
class="generator">{{ _('Show secret') }}</button>
<input type="text" class="secret" type="text" value="" readonly>
</td>
{% else %}
<td class="not-generated">({{ _('Not yet generated.') }})</td>
{% endif %}
</tr>
</table>
<div class="listing-footer">
<button type="submit">{{ _('Reset secret') if key else _('Generate Keys') }}</button>
</div>
</form>
</div>
</section>
{% endblock %}
86 changes: 85 additions & 1 deletion mkt/developers/tests/test_views_payments.py
Expand Up @@ -3,6 +3,7 @@
from django.core.exceptions import ObjectDoesNotExist

import mock
from mock import ANY
from nose.tools import eq_, ok_, raises
from pyquery import PyQuery as pq

Expand All @@ -16,7 +17,7 @@

import mkt
from mkt.developers.models import (AddonPaymentAccount, PaymentAccount,
SolitudeSeller)
SolitudeSeller, UserInappKey)
from mkt.site.fixtures import fixture
from mkt.webapps.models import AddonExcludedRegion as AER, ContentRating

Expand Down Expand Up @@ -136,6 +137,89 @@ def test_developer(self, solitude):
eq_(resp.content, 'shhh!')


class InappKeysTest(InappTest):
fixtures = fixture('webapp_337141', 'user_999')

def setUp(self):
super(InappKeysTest, self).setUp()
self.create_switch('in-app-sandbox')
self.url = reverse('mkt.developers.apps.in_app_keys')
self.seller_uri = '/seller/1/'
self.product_pk = 2

def setup_solitude(self, solitude):
solitude.post_seller.return_value = {'resource_uri': self.seller_uri}
solitude.post_product.return_value = {'resource_pk': self.product_pk}


@mock.patch('mkt.developers.models.client')
class TestInappKeys(InappKeysTest):

def test_logged_out(self, solitude):
self.client.logout()
self.assertLoginRequired(self.client.get(self.url))

def test_no_key(self, solitude):
res = self.client.get(self.url)
eq_(res.status_code, 200)
eq_(res.context['key'], None)

def test_key_generation(self, solitude):
self.setup_solitude(solitude)
res = self.client.post(self.url)

assert res['Location'].endswith(self.url), res
assert solitude.post_seller.called, 'solitude seller not created'
assert solitude.post_product.called, 'seller product not created'
key = UserInappKey.objects.get()
eq_(key.solitude_seller.resource_uri, self.seller_uri)
eq_(key.seller_product_pk, self.product_pk)

def test_reset(self, solitude):
self.setup_solitude(solitude)
key = UserInappKey.create(self.user)
product = mock.Mock()
solitude.api.generic.product.return_value = product

self.client.post(self.url)
product.patch.assert_called_with(data={'secret': ANY})
solitude.api.generic.product.assert_called_with(key.seller_product_pk)


@mock.patch('mkt.developers.models.client')
class TestInappKeySecret(InappKeysTest):

def setUp(self):
super(TestInappKeySecret, self).setUp()

def setup_objects(self, solitude):
self.setup_solitude(solitude)
key = UserInappKey.create(self.user)
self.url = reverse('mkt.developers.apps.in_app_key_secret',
args=[key.pk])

def test_logged_out(self, solitude):
self.setup_objects(solitude)
self.client.logout()
self.assertLoginRequired(self.client.get(self.url))

def test_different(self, solitude):
self.setup_objects(solitude)
self.login(self.other)
eq_(self.client.get(self.url).status_code, 403)

def test_secret(self, solitude):
self.setup_objects(solitude)
secret = 'not telling'
product = mock.Mock()
product.get.return_value = {'secret': secret}
solitude.api.generic.product.return_value = product

res = self.client.get(self.url)
eq_(res.status_code, 200)
eq_(res.content, secret)


class TestPayments(amo.tests.TestCase):
fixtures = ['base/apps', 'base/users', 'webapps/337141-steamcube',
'market/prices']
Expand Down
5 changes: 5 additions & 0 deletions mkt/developers/urls.py
Expand Up @@ -122,8 +122,13 @@ def bango_patterns(prefix):
views.standalone_upload_detail,
name='mkt.developers.standalone_upload_detail'),

# Standalone tools.
url('^upload-manifest$', views.upload_manifest,
name='mkt.developers.upload_manifest'),
url('^in-app-keys/$', views_payments.in_app_keys,
name='mkt.developers.apps.in_app_keys'),
url('^in-app-key-secret/([^/]+)$', views_payments.in_app_key_secret,
name='mkt.developers.apps.in_app_key_secret'),

# URLs for a single app.
url('^app/%s/' % amo.APP_SLUG, include(app_detail_patterns)),
Expand Down
36 changes: 36 additions & 0 deletions mkt/developers/views_payments.py
Expand Up @@ -216,6 +216,42 @@ def payments_accounts_delete(request, id):
return http.HttpResponse('success')


@login_required
@waffle_switch('in-app-sandbox')
def in_app_keys(request):
keys = (models.UserInappKey.uncached
.filter(solitude_seller__user=request.amo_user))
# TODO(Kumar) support multiple test keys. For now there's only one.
if keys.count():
key = keys.get()
else:
key = None
if request.method == 'POST':
if key:
key.reset()
messages.success(request, _('Secret was reset successfully.'))
else:

This comment has been minimized.

Copy link
@andymckay

andymckay Feb 12, 2013

Contributor

key is unused

This comment has been minimized.

Copy link
@kumar303

kumar303 Feb 12, 2013

Author Contributor

oh yeah, thanks. I had key.create() in there but it was redundant

key = models.UserInappKey.create(request.amo_user)
messages.success(request,
_('Key and secret were created successfully.'))
return redirect(reverse('mkt.developers.apps.in_app_keys'))

return jingo.render(request, 'developers/payments/in-app-keys.html',
{'key': key})


@login_required
@waffle_switch('in-app-sandbox')
def in_app_key_secret(request, pk):
key = (models.UserInappKey.uncached
.filter(solitude_seller__user=request.amo_user, pk=pk))
if not key.count():
# Either the record does not exist or it's not owned by the
# logged in user.
return http.HttpResponseForbidden()
return http.HttpResponse(key.get().secret())


@login_required
@waffle_switch('in-app-payments')
@dev_required(owner_for_post=True, webapp=True)
Expand Down

0 comments on commit 3e9334a

Please sign in to comment.