Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format

## Unreleased

## [1.5.0][1.5.0] - 2024-12-19

feat: Add DeviceActivity support for POS Gateway integration

- Add DeviceActivity resource with create() and get_status() methods
- Support PUBLIC authentication for DeviceActivity APIs
- Add X-Razorpay-Device-Mode header injection for wired/wireless modes
- Add DeviceMode constants (WIRED, WIRELESS)
- Enhance Client to support public_auth parameter
- Add comprehensive test coverage and mock responses
- Fix test_multiple_client URL mismatch
- Maintain backward compatibility with existing APIs

Endpoints:
- POST /v1/devices/activity (create device activity)
- GET /v1/devices/activity/{id} (get activity status)

## [1.4.2][1.4.2] - 2024-03-19

feat: Added new API endpoints
Expand Down
18 changes: 17 additions & 1 deletion razorpay/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,25 @@ def request(self, method, path, **options):
"""
options = self._update_user_agent_header(options)

# Determine authentication type
use_public_auth = options.pop('use_public_auth', False)
auth_to_use = self.auth

if use_public_auth:
# For public auth, use key_id only
if self.auth and isinstance(self.auth, tuple) and len(self.auth) >= 1:
auth_to_use = (self.auth[0], '') # Use key_id only, empty key_secret

# Inject device mode header if provided
device_mode = options.pop('device_mode', None)
if device_mode is not None:
if 'headers' not in options:
options['headers'] = {}
options['headers']['X-Razorpay-Device-Mode'] = device_mode

url = "{}{}".format(self.base_url, path)

response = getattr(self.session, method)(url, auth=self.auth,
response = getattr(self.session, method)(url, auth=auth_to_use,
verify=self.cert_path,
**options)
if ((response.status_code >= HTTP_STATUS_CODE.OK) and
Expand Down
3 changes: 3 additions & 0 deletions razorpay/constants/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class DeviceMode:
WIRED = "wired"
WIRELESS = "wireless"
2 changes: 2 additions & 0 deletions razorpay/constants/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ class URL(object):
DOCUMENT= "/documents"
DISPUTE= "/disputes"

DEVICE_ACTIVITY_URL = "/devices/activity"

2 changes: 2 additions & 0 deletions razorpay/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .webhook import Webhook
from .document import Document
from .dispute import Dispute
from .device_activity import DeviceActivity

__all__ = [
'Payment',
Expand Down Expand Up @@ -50,4 +51,5 @@
'Webhook',
'Document',
'Dispute',
'DeviceActivity',
]
66 changes: 66 additions & 0 deletions razorpay/resources/device_activity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Any, Dict, Optional

from .base import Resource
from ..constants.url import URL
from ..constants.device import DeviceMode
from ..errors import BadRequestError


class DeviceActivity(Resource):
def __init__(self, client=None):
super(DeviceActivity, self).__init__(client)
self.base_url = URL.V1 + URL.DEVICE_ACTIVITY_URL

def _validate_device_mode(self, mode: Optional[str]) -> Optional[str]:
"""
Validate device communication mode

Args:
mode: Device communication mode ("wired" or "wireless")

Returns:
Validated mode or None if mode is None

Raises:
BadRequestError: If mode is invalid
"""
if mode is not None:
if mode not in (DeviceMode.WIRED, DeviceMode.WIRELESS):
raise BadRequestError("Invalid device mode. Allowed values are 'wired' and 'wireless'.")
return mode
return None

def create(self, data: Dict[str, Any], mode: Optional[str] = None, **kwargs) -> Dict[str, Any]:
"""
Create a new device activity for POS gateway

Args:
data: Dictionary containing device activity data in the format expected by rzp-pos-gateway
mode: Device communication mode ("wired" or "wireless")

Returns:
DeviceActivity object
"""
device_mode = self._validate_device_mode(mode)

url = self.base_url
return self.post_url(url, data, device_mode=device_mode, use_public_auth=True, **kwargs)

def get_status(self, activity_id: str, mode: Optional[str] = None, **kwargs) -> Dict[str, Any]:
"""
Get the status of a device activity

Args:
activity_id: Activity ID to fetch status for
mode: Device communication mode ("wired" or "wireless")

Returns:
DeviceActivity object with current status
"""
if not activity_id:
raise BadRequestError("Activity ID must be provided")

device_mode = self._validate_device_mode(mode)

url = f"{self.base_url}/{activity_id}"
return self.get_url(url, {}, device_mode=device_mode, use_public_auth=True, **kwargs)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="razorpay",
version="1.4.2",
version="1.5.0",
description="Razorpay Python Client",
long_description=readme_content,
long_description_content_type='text/markdown',
Expand Down
1 change: 1 addition & 0 deletions tests/mocks/fake_device_activity.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id": "act_123", "status": "created", "mode": "wired"}
1 change: 1 addition & 0 deletions tests/mocks/fake_device_activity_status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id": "act_123", "status": "in_progress", "mode": "wireless"}
38 changes: 38 additions & 0 deletions tests/test_client_device_activity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import unittest
import responses
import json

from .helpers import mock_file, ClientTestCase
from razorpay.errors import BadRequestError
import razorpay


class TestClientDeviceActivity(ClientTestCase):

def setUp(self):
super(TestClientDeviceActivity, self).setUp()
self.device_activity_base_url = f"{self.base_url}/devices/activity"
# Device APIs automatically use public authentication (key_id only)
# by passing use_public_auth=True internally in device_activity.py
self.public_client = razorpay.Client(auth=('key_id', 'key_secret'))

@responses.activate
def test_create_device_activity(self):
result = mock_file('fake_device_activity')
url = self.device_activity_base_url
responses.add(responses.POST, url, status=200,
body=json.dumps(result), match_querystring=True)
self.assertEqual(self.public_client.device_activity.create({'foo': 'bar'}, mode='wired'), result)

@responses.activate
def test_get_status_device_activity(self):
activity_id = 'act_123'
result = mock_file('fake_device_activity_status')
url = f"{self.device_activity_base_url}/{activity_id}"
responses.add(responses.GET, url, status=200,
body=json.dumps(result), match_querystring=True)
self.assertEqual(self.public_client.device_activity.get_status(activity_id, mode='wireless'), result)

def test_invalid_mode_raises(self):
with self.assertRaises(BadRequestError):
self.public_client.device_activity.create({'foo': 'bar'}, mode='invalid')
4 changes: 2 additions & 2 deletions tests/test_multiple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TestClientPayment(ClientTestCase):
def setUp(self):
super(TestClientPayment, self).setUp()
self.base_url = '{}/payments'.format(self.base_url)
self.secondary_base_url = '{}/payments'.format(self.secondary_url)
self.secondary_base_url = '{}/v1/payments'.format(self.secondary_url)

@responses.activate
def test_payment_primary_url(self):
Expand All @@ -20,7 +20,7 @@ def test_payment_primary_url(self):
body=json.dumps(result), match_querystring=True)
self.assertEqual(self.client.payment.all(), result)

@unittest.skip
@responses.activate
def test_payment_secondary_url(self):
result = mock_file('payment_collection')
url = self.secondary_base_url
Expand Down
Loading