Skip to content

Commit c1a9b4d

Browse files
authored
Merge pull request #309 from razorpay/feature/pos-gateway-integration
ISS-971955 feat: Add DeviceActivity support for POS Gateway integration
2 parents 0c4f098 + f0f8061 commit c1a9b4d

File tree

11 files changed

+150
-4
lines changed

11 files changed

+150
-4
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format
44

55
## Unreleased
66

7+
## [1.5.0][1.5.0] - 2024-12-19
8+
9+
feat: Add DeviceActivity support for POS Gateway integration
10+
11+
- Add DeviceActivity resource with create() and get_status() methods
12+
- Support PUBLIC authentication for DeviceActivity APIs
13+
- Add X-Razorpay-Device-Mode header injection for wired/wireless modes
14+
- Add DeviceMode constants (WIRED, WIRELESS)
15+
- Enhance Client to support public_auth parameter
16+
- Add comprehensive test coverage and mock responses
17+
- Fix test_multiple_client URL mismatch
18+
- Maintain backward compatibility with existing APIs
19+
20+
Endpoints:
21+
- POST /v1/devices/activity (create device activity)
22+
- GET /v1/devices/activity/{id} (get activity status)
23+
724
## [1.4.2][1.4.2] - 2024-03-19
825

926
feat: Added new API endpoints

razorpay/client.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,25 @@ def request(self, method, path, **options):
115115
"""
116116
options = self._update_user_agent_header(options)
117117

118+
# Determine authentication type
119+
use_public_auth = options.pop('use_public_auth', False)
120+
auth_to_use = self.auth
121+
122+
if use_public_auth:
123+
# For public auth, use key_id only
124+
if self.auth and isinstance(self.auth, tuple) and len(self.auth) >= 1:
125+
auth_to_use = (self.auth[0], '') # Use key_id only, empty key_secret
126+
127+
# Inject device mode header if provided
128+
device_mode = options.pop('device_mode', None)
129+
if device_mode is not None:
130+
if 'headers' not in options:
131+
options['headers'] = {}
132+
options['headers']['X-Razorpay-Device-Mode'] = device_mode
133+
118134
url = "{}{}".format(self.base_url, path)
119135

120-
response = getattr(self.session, method)(url, auth=self.auth,
136+
response = getattr(self.session, method)(url, auth=auth_to_use,
121137
verify=self.cert_path,
122138
**options)
123139
if ((response.status_code >= HTTP_STATUS_CODE.OK) and

razorpay/constants/device.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class DeviceMode:
2+
WIRED = "wired"
3+
WIRELESS = "wireless"

razorpay/constants/url.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ class URL(object):
2929
DOCUMENT= "/documents"
3030
DISPUTE= "/disputes"
3131

32+
DEVICE_ACTIVITY_URL = "/devices/activity"
33+

razorpay/resources/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .webhook import Webhook
2424
from .document import Document
2525
from .dispute import Dispute
26+
from .device_activity import DeviceActivity
2627

2728
__all__ = [
2829
'Payment',
@@ -50,4 +51,5 @@
5051
'Webhook',
5152
'Document',
5253
'Dispute',
54+
'DeviceActivity',
5355
]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from typing import Any, Dict, Optional
2+
3+
from .base import Resource
4+
from ..constants.url import URL
5+
from ..constants.device import DeviceMode
6+
from ..errors import BadRequestError
7+
8+
9+
class DeviceActivity(Resource):
10+
def __init__(self, client=None):
11+
super(DeviceActivity, self).__init__(client)
12+
self.base_url = URL.V1 + URL.DEVICE_ACTIVITY_URL
13+
14+
def _validate_device_mode(self, mode: Optional[str]) -> Optional[str]:
15+
"""
16+
Validate device communication mode
17+
18+
Args:
19+
mode: Device communication mode ("wired" or "wireless")
20+
21+
Returns:
22+
Validated mode or None if mode is None
23+
24+
Raises:
25+
BadRequestError: If mode is invalid
26+
"""
27+
if mode is not None:
28+
if mode not in (DeviceMode.WIRED, DeviceMode.WIRELESS):
29+
raise BadRequestError("Invalid device mode. Allowed values are 'wired' and 'wireless'.")
30+
return mode
31+
return None
32+
33+
def create(self, data: Dict[str, Any], mode: Optional[str] = None, **kwargs) -> Dict[str, Any]:
34+
"""
35+
Create a new device activity for POS gateway
36+
37+
Args:
38+
data: Dictionary containing device activity data in the format expected by rzp-pos-gateway
39+
mode: Device communication mode ("wired" or "wireless")
40+
41+
Returns:
42+
DeviceActivity object
43+
"""
44+
device_mode = self._validate_device_mode(mode)
45+
46+
url = self.base_url
47+
return self.post_url(url, data, device_mode=device_mode, use_public_auth=True, **kwargs)
48+
49+
def get_status(self, activity_id: str, mode: Optional[str] = None, **kwargs) -> Dict[str, Any]:
50+
"""
51+
Get the status of a device activity
52+
53+
Args:
54+
activity_id: Activity ID to fetch status for
55+
mode: Device communication mode ("wired" or "wireless")
56+
57+
Returns:
58+
DeviceActivity object with current status
59+
"""
60+
if not activity_id:
61+
raise BadRequestError("Activity ID must be provided")
62+
63+
device_mode = self._validate_device_mode(mode)
64+
65+
url = f"{self.base_url}/{activity_id}"
66+
return self.get_url(url, {}, device_mode=device_mode, use_public_auth=True, **kwargs)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name="razorpay",
8-
version="1.4.2",
8+
version="1.5.0",
99
description="Razorpay Python Client",
1010
long_description=readme_content,
1111
long_description_content_type='text/markdown',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "act_123", "status": "created", "mode": "wired"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "act_123", "status": "in_progress", "mode": "wireless"}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import unittest
2+
import responses
3+
import json
4+
5+
from .helpers import mock_file, ClientTestCase
6+
from razorpay.errors import BadRequestError
7+
import razorpay
8+
9+
10+
class TestClientDeviceActivity(ClientTestCase):
11+
12+
def setUp(self):
13+
super(TestClientDeviceActivity, self).setUp()
14+
self.device_activity_base_url = f"{self.base_url}/devices/activity"
15+
# Device APIs automatically use public authentication (key_id only)
16+
# by passing use_public_auth=True internally in device_activity.py
17+
self.public_client = razorpay.Client(auth=('key_id', 'key_secret'))
18+
19+
@responses.activate
20+
def test_create_device_activity(self):
21+
result = mock_file('fake_device_activity')
22+
url = self.device_activity_base_url
23+
responses.add(responses.POST, url, status=200,
24+
body=json.dumps(result), match_querystring=True)
25+
self.assertEqual(self.public_client.device_activity.create({'foo': 'bar'}, mode='wired'), result)
26+
27+
@responses.activate
28+
def test_get_status_device_activity(self):
29+
activity_id = 'act_123'
30+
result = mock_file('fake_device_activity_status')
31+
url = f"{self.device_activity_base_url}/{activity_id}"
32+
responses.add(responses.GET, url, status=200,
33+
body=json.dumps(result), match_querystring=True)
34+
self.assertEqual(self.public_client.device_activity.get_status(activity_id, mode='wireless'), result)
35+
36+
def test_invalid_mode_raises(self):
37+
with self.assertRaises(BadRequestError):
38+
self.public_client.device_activity.create({'foo': 'bar'}, mode='invalid')

0 commit comments

Comments
 (0)