Skip to content

Commit 2aa3d16

Browse files
committed
Added /dialog/subscribe API
1 parent acb204e commit 2aa3d16

3 files changed

Lines changed: 88 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* Ignored HMAC character capitalization
66
([#93](https://github.com/laterpay/laterpay-client-python/issues/93))
7+
* Added support for ``/dialog/subscribe`` and LaterPay's subscription system
78

89
## 5.0.0
910

laterpay/__init__.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
_PRICING_RE = re.compile(r'[A-Z]{3}\d+')
2828
_EXPIRY_RE = re.compile(r'^(\+?\d+)$')
29+
_SUB_ID_RE = re.compile(r'^[a-zA-Z0-9_-]{1,128}$')
2930

3031

3132
class InvalidTokenException(Exception):
@@ -62,7 +63,7 @@ class ItemDefinition(object):
6263
For Single item purchases: http://docs.laterpay.net/platform/dialogs/buy/
6364
"""
6465

65-
def __init__(self, item_id, pricing, url, title, expiry=None):
66+
def __init__(self, item_id, pricing, url, title, expiry=None, sub_id=None, period=None):
6667

6768
for price in pricing.split(','):
6869
if not _PRICING_RE.match(price):
@@ -79,6 +80,22 @@ def __init__(self, item_id, pricing, url, title, expiry=None):
7980
'title': title,
8081
'expiry': expiry,
8182
}
83+
if sub_id is not None:
84+
if _SUB_ID_RE.match(sub_id):
85+
self.data['sub_id'] = sub_id
86+
else:
87+
raise InvalidItemDefinition(
88+
"Invalid sub_id value '%s'. It can be any string consisting of lowercase or "
89+
"uppercase ASCII characters, digits, underscore and hyphen, the length of "
90+
"which is between 1 and 128 characters." % sub_id
91+
)
92+
if isinstance(period, int) and 3600 <= period <= 31536000:
93+
self.data['period'] = period
94+
else:
95+
raise InvalidItemDefinition(
96+
"Period not set or invalid value '%s' for period. The subscription period "
97+
"must be an int in the range [3600, 31536000] (including)." % period
98+
)
8299

83100

84101
class LaterPayClient(object):
@@ -282,6 +299,14 @@ def get_add_url(self, item_definition, *args, **kwargs):
282299
"""
283300
return self._get_web_url(item_definition, 'add', *args, **kwargs)
284301

302+
def get_subscribe_url(self, item_definition, *args, **kwargs):
303+
"""
304+
Get the URL at which a user can subscribe to an item.
305+
306+
http://docs.laterpay.net/platform/dialogs/subscribe/
307+
"""
308+
return self._get_web_url(item_definition, 'subscribe', *args, **kwargs)
309+
285310
def has_token(self):
286311
"""
287312
Do we have an identifier token.

tests/test_client.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,32 @@
1919

2020
class TestItemDefinition(unittest.TestCase):
2121

22-
def test_item_definition(self):
22+
def test_invalid_pricing(self):
2323
with self.assertRaises(InvalidItemDefinition):
2424
ItemDefinition(1, '', '', 'title')
25+
26+
def test_invalid_expiry(self):
2527
with self.assertRaises(InvalidItemDefinition):
2628
ItemDefinition(1, 'EUR20', 'http://foo.invalid', 'title', expiry="illegal123")
2729

28-
it = ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', expiry='+100')
30+
def test_invalid_sub_id(self):
31+
with self.assertRaisesRegexp(InvalidItemDefinition, r'^Invalid sub_id value '):
32+
ItemDefinition(1, 'EUR20', 'http://example.com', 'title', sub_id='', period=3600)
33+
with self.assertRaisesRegexp(InvalidItemDefinition, r'^Invalid sub_id value '):
34+
ItemDefinition(1, 'EUR20', 'http://example.com', 'title', sub_id='a' * 129, period=3600)
35+
with self.assertRaisesRegexp(InvalidItemDefinition, r'^Invalid sub_id value '):
36+
ItemDefinition(1, 'EUR20', 'http://example.com', 'title', sub_id='ä', period=3600)
37+
38+
def test_invalid_period(self):
39+
with self.assertRaisesRegexp(InvalidItemDefinition, r'Period not set or invalid value'):
40+
ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', sub_id='a', period=3599)
41+
with self.assertRaisesRegexp(InvalidItemDefinition, r'Period not set or invalid value'):
42+
ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', sub_id='a', period=31536001)
43+
with self.assertRaisesRegexp(InvalidItemDefinition, r'Period not set or invalid value'):
44+
ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', sub_id='a', period='12345')
2945

46+
def test_item_definition(self):
47+
it = ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', expiry='+100')
3048
self.assertEqual(it.data, {
3149
'article_id': 1,
3250
'expiry': '+100',
@@ -35,6 +53,22 @@ def test_item_definition(self):
3553
'url': 'http://example.com/t',
3654
})
3755

56+
def test_sub_id(self):
57+
# Test sub_id_bounds
58+
ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', sub_id='a', period=3600)
59+
ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', sub_id='a' * 128, period=31536000)
60+
61+
it = ItemDefinition(1, 'EUR20', 'http://example.com/t', 'title', sub_id='abc', period=12345)
62+
self.assertEqual(it.data, {
63+
'article_id': 1,
64+
'expiry': None,
65+
'period': 12345,
66+
'pricing': 'EUR20',
67+
'sub_id': 'abc',
68+
'title': 'title',
69+
'url': 'http://example.com/t',
70+
})
71+
3872

3973
class TestLaterPayClient(unittest.TestCase):
4074

@@ -206,6 +240,31 @@ def test_get_buy_url(self):
206240
self.assertQueryString(url, 'something', value='else')
207241
self.assertQueryString(url, 'BLUB', value=[b'b1', 'b2', u'u1', 'u2'])
208242

243+
def test_get_subscribe_url(self):
244+
item = ItemDefinition(1, 'EUR20', 'http://example.net/t', 'title', sub_id='a0_-9Z', period=12345)
245+
url = self.lp.get_subscribe_url(
246+
item,
247+
product_key='some-product-key',
248+
dialog=False,
249+
return_url='http://return.url/foo?bar=buz&lorem=ipsum',
250+
failure_url='http://failure.url/FOO?BAR=BUZ&LOREM=IPSUM',
251+
something='else',
252+
period=12345,
253+
BLUB=[u'u2', b'b1', b'b2', u'u1'],
254+
)
255+
self.assertFalse(
256+
self.assertQueryString(url, 'expiry'),
257+
'expiry url param is "None". Should be omitted.'
258+
)
259+
self.assertQueryString(url, 'sub_id', value='a0_-9Z')
260+
self.assertQueryString(url, 'product_key', value='some-product-key')
261+
self.assertTrue(url.startswith('https://web.laterpay.net/subscribe?'))
262+
self.assertQueryString(url, 'return_url', value='http://return.url/foo?bar=buz&lorem=ipsum')
263+
self.assertQueryString(url, 'failure_url', value='http://failure.url/FOO?BAR=BUZ&LOREM=IPSUM')
264+
self.assertQueryString(url, 'something', value='else')
265+
self.assertQueryString(url, 'period', value='12345')
266+
self.assertQueryString(url, 'BLUB', value=[b'b1', 'b2', u'u1', 'u2'])
267+
209268
def test_get_login_dialog_url_with_use_dialog_api_false(self):
210269
url = self.lp.get_login_dialog_url('http://example.org')
211270
self.assertEqual(str(furl(url).path), '/account/dialog/login')

0 commit comments

Comments
 (0)