Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ E2E_TERMINAL_GROUP_API_KEY_GROUP_DEFAULT=your-e2e-terminal-group-api-key-for-def
# Optional custom base URL for dev-backed examples and terminal endpoint E2E.
# MSP_SDK_BUILD_PROFILE=dev
# MSP_SDK_ALLOW_CUSTOM_BASE_URL=1
# MSP_SDK_CUSTOM_BASE_URL=your-custom-base-url
# MSP_SDK_CUSTOM_BASE_URL=your-custom-base-url
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ multisafepay_sdk: Sdk = Sdk(api_key='<api_key>', is_production=True)

### Initialize with scoped credentials

Use `ScopedCredentialResolver` when the API key must change per auth scope.
Use `ScopedCredentialResolver` when different API keys must be selected per auth scope.
When `credential_resolver` is provided, `api_key` becomes optional.

```python
Expand All @@ -90,6 +90,7 @@ credential_resolver = ScopedCredentialResolver(
terminal_group_api_keys={
"Default": "<terminal_group_api_key>",
},
partner_affiliate_api_key="<partner_api_key>",
)

sdk = Sdk(
Expand All @@ -98,6 +99,42 @@ sdk = Sdk(
)
```

Resolver behavior:

- `default_api_key` is used for regular account-scoped requests.
- `partner_affiliate_api_key` is used for partner-affiliate scoped requests and falls back to `default_api_key` when omitted.

### Terminal and terminal-group operations

The SDK exposes dedicated managers for POS terminal listing/creation and for listing terminals inside a specific terminal group.

```python
from multisafepay.client import ScopedCredentialResolver
from multisafepay import Sdk


credential_resolver = ScopedCredentialResolver(
default_api_key="<default_api_key>",
partner_affiliate_api_key="<partner_api_key>",
)

sdk = Sdk(
is_production=False,
credential_resolver=credential_resolver,
)

terminal_manager = sdk.get_terminal_manager()
terminal_group_manager = sdk.get_terminal_group_manager()

terminals = terminal_manager.get_terminals(options={"limit": 10, "page": 1})
group_terminals = terminal_group_manager.get_terminals_by_group(
terminal_group_id="<terminal_group_id>",
options={"limit": 10, "page": 1},
)
```

See terminal examples in `examples/terminal_manager/` and `examples/terminal_group_manager/`.

### Development-only custom base URL override

By default, the SDK only targets:
Expand Down Expand Up @@ -175,6 +212,20 @@ When omitted, E2E defaults to `testapi.multisafepay.com`.
The e2e suite does not use the shared `API_KEY` variable or the shared `MSP_SDK_*`
custom base URL settings.

Terminal endpoint examples and E2E checks use a dev-backed base URL because those endpoints are not exercised against the default shared E2E target.

```bash
export API_KEY="<account_api_key>"
export PARTNER_API_KEY="<partner_api_key>" # optional
export MSP_SDK_BUILD_PROFILE=dev
export MSP_SDK_ALLOW_CUSTOM_BASE_URL=1
export MSP_SDK_CUSTOM_BASE_URL="https://dev-api.example.com/v1/"
export E2E_CLOUD_POS_TERMINAL_ID="<terminal_id>"
# Optional: set when you want to skip automatic terminal-group lookup
export CLOUD_POS_TERMINAL_GROUP_ID="<terminal_group_id>"
Comment on lines +224 to +225
make test-e2e
```

## Support

Create an issue on this repository or email <a href="mailto:integration@multisafepay.com">
Expand Down
57 changes: 57 additions & 0 deletions examples/terminal_group_manager/get_terminals_by_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright (c) MultiSafepay, Inc. All rights reserved.

# This file is licensed under the Open Software License (OSL) version 3.0.
# For a copy of the license, see the LICENSE.txt file in the project root.

# See the DISCLAIMER.md file for disclaimer details.

import os

from dotenv import load_dotenv

from multisafepay import Sdk
from multisafepay.client import ScopedCredentialResolver

# Load environment variables from a .env file
load_dotenv()

default_account_api_key = (os.getenv("API_KEY") or "").strip()
partner_affiliate_api_key = (os.getenv("PARTNER_API_KEY") or "").strip()

terminal_group_id = os.getenv(
"CLOUD_POS_TERMINAL_GROUP_ID",
"<terminal_group_id>",
).strip()

if __name__ == "__main__":
# get_terminals_by_group → partner_affiliate scope → resolver returns
# partner_affiliate_api_key, falls back to default_api_key
resolver_kwargs = {
"default_api_key": default_account_api_key,
"partner_affiliate_api_key": partner_affiliate_api_key,
}

credential_resolver = ScopedCredentialResolver(**resolver_kwargs)

multisafepay_sdk = Sdk(
is_production=False,
credential_resolver=credential_resolver,
)

# Get the 'TerminalGroup' manager from the SDK
terminal_group_manager = multisafepay_sdk.get_terminal_group_manager()

# Define optional pagination parameters
options = {
"limit": 10,
"page": 1,
}

# Fetch terminals assigned to the specified terminal group
terminals_by_group_response = terminal_group_manager.get_terminals_by_group(
terminal_group_id=terminal_group_id,
options=options,
)

# Print the terminal listing data
print(terminals_by_group_response.get_data())
61 changes: 61 additions & 0 deletions examples/terminal_manager/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) MultiSafepay, Inc. All rights reserved.

# This file is licensed under the Open Software License (OSL) version 3.0.
# For a copy of the license, see the LICENSE.txt file in the project root.

# See the DISCLAIMER.md file for disclaimer details.

import os

from dotenv import load_dotenv

from multisafepay import Sdk
from multisafepay.api.paths.terminals.request.create_terminal_request import (
CreateTerminalRequest,
)
from multisafepay.client import ScopedCredentialResolver

# Load environment variables from a .env file
load_dotenv()

default_account_api_key = (os.getenv("API_KEY") or "").strip()
partner_affiliate_api_key = (os.getenv("PARTNER_API_KEY") or "").strip()

terminal_group_id_raw = os.getenv(
"CLOUD_POS_TERMINAL_GROUP_ID",
"<terminal_group_id>",
).strip()

if __name__ == "__main__":
# create_terminal → default scope → resolver returns default_api_key
Comment thread
danielcivit marked this conversation as resolved.
Outdated
terminal_group_id = int(terminal_group_id_raw)

resolver_kwargs = {
"default_api_key": default_account_api_key,
"partner_affiliate_api_key": partner_affiliate_api_key,
}

credential_resolver = ScopedCredentialResolver(**resolver_kwargs)

multisafepay_sdk = Sdk(
is_production=False,
credential_resolver=credential_resolver,
)

# Get the 'Terminal' manager from the SDK
terminal_manager = multisafepay_sdk.get_terminal_manager()

# Build the create terminal request
create_request = (
CreateTerminalRequest()
.add_provider("CTAP")
.add_group_id(terminal_group_id)
.add_name("Demo POS Terminal")
)

# Create a new POS terminal
terminal_response = terminal_manager.create_terminal(create_request)

# Print the created terminal data
terminal_data = terminal_response.get_data()
print(terminal_data)
49 changes: 49 additions & 0 deletions examples/terminal_manager/get_terminals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) MultiSafepay, Inc. All rights reserved.

# This file is licensed under the Open Software License (OSL) version 3.0.
# For a copy of the license, see the LICENSE.txt file in the project root.

# See the DISCLAIMER.md file for disclaimer details.

import os

from dotenv import load_dotenv

from multisafepay import Sdk
from multisafepay.client import ScopedCredentialResolver

# Load environment variables from a .env file
load_dotenv()

default_account_api_key = (os.getenv("API_KEY") or "").strip()
partner_affiliate_api_key = (os.getenv("PARTNER_API_KEY") or "").strip()

if __name__ == "__main__":
# get_terminals → partner_affiliate scope → resolver returns
# partner_affiliate_api_key, falls back to default_api_key
resolver_kwargs = {
"default_api_key": default_account_api_key,
"partner_affiliate_api_key": partner_affiliate_api_key,
}

credential_resolver = ScopedCredentialResolver(**resolver_kwargs)

multisafepay_sdk = Sdk(
is_production=False,
credential_resolver=credential_resolver,
)

# Get the 'Terminal' manager from the SDK
terminal_manager = multisafepay_sdk.get_terminal_manager()

# Define optional pagination parameters
options = {
"limit": 10,
"page": 1,
}

# Fetch terminals for the account
terminals_response = terminal_manager.get_terminals(options=options)

# Print the terminal listing data
print(terminals_response.get_data())
16 changes: 16 additions & 0 deletions src/multisafepay/api/paths/terminal_groups/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) MultiSafepay, Inc. All rights reserved.

# This file is licensed under the Open Software License (OSL) version 3.0.
# For a copy of the license, see the LICENSE.txt file in the project root.

# See the DISCLAIMER.md file for disclaimer details.

"""Terminal group API endpoints."""

from multisafepay.api.paths.terminal_groups.terminal_group_manager import (
TerminalGroupManager,
)

__all__ = [
"TerminalGroupManager",
]
110 changes: 110 additions & 0 deletions src/multisafepay/api/paths/terminal_groups/terminal_group_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright (c) MultiSafepay, Inc. All rights reserved.

# This file is licensed under the Open Software License (OSL) version 3.0.
# For a copy of the license, see the LICENSE.txt file in the project root.

# See the DISCLAIMER.md file for disclaimer details.

"""Terminal group manager for `/json/terminal-groups/{terminal_group_id}/terminals`."""

from multisafepay.api.base.abstract_manager import AbstractManager
from multisafepay.api.base.listings.listing_pager import ListingPager
from multisafepay.api.base.listings.pager import Pager
from multisafepay.api.base.response.api_response import ApiResponse
from multisafepay.api.base.response.custom_api_response import (
CustomApiResponse,
)
from multisafepay.api.paths.terminals.response.terminal import Terminal
from multisafepay.client.client import Client
from multisafepay.client.credential_resolver import (
AuthScope,
ScopedCredentialResolver,
)
from multisafepay.util.message import MessageList, gen_could_not_created_msg
from pydantic import ValidationError

ALLOWED_OPTIONS = {
"page": "",
"limit": "",
}


class TerminalGroupManager(AbstractManager):
"""A class representing the TerminalGroupManager."""

def __init__(self: "TerminalGroupManager", client: Client) -> None:
"""
Initialize the TerminalGroupManager with a client.

Parameters
----------
client (Client): The client used to make API requests.

"""
super().__init__(client)

@staticmethod
def __custom_terminal_listing_response(
response: ApiResponse,
) -> CustomApiResponse:
args: dict = {
**response.dict(),
"data": None,
}

pager = None
raw_pager = response.get_pager()
if isinstance(raw_pager, dict):
pager = Pager.from_dict(raw_pager.copy())

try:
args["data"] = ListingPager(
data=response.get_body_data().copy(),
pager=pager,
class_type=Terminal,
)
except (AttributeError, TypeError, ValidationError):
args["warnings"] = MessageList().add_message(
gen_could_not_created_msg("Listing Terminal"),
)

return CustomApiResponse(**args)

def get_terminals_by_group(
self: "TerminalGroupManager",
terminal_group_id: str,
options: dict = None,
) -> CustomApiResponse:
"""
List POS terminals for the given terminal group.

Parameters
----------
terminal_group_id (str): Terminal group identifier.
options (dict): Request options (`page`, `limit`). Defaults to None.

Returns
-------
CustomApiResponse: The response containing terminal listing data.

"""
if options is None:
options = {}
options = {k: v for k, v in options.items() if k in ALLOWED_OPTIONS}

encoded_terminal_group_id = self.encode_path_segment(terminal_group_id)
endpoint = (
f"json/terminal-groups/{encoded_terminal_group_id}/terminals"
)
context = {"terminal_group_id": terminal_group_id}
response = self.client.create_get_request(
endpoint=endpoint,
params=options,
context=context,
auth_scope=AuthScope(
scope=ScopedCredentialResolver.AUTH_SCOPE_PARTNER_AFFILIATE,
),
)
return TerminalGroupManager.__custom_terminal_listing_response(
response,
)
Loading
Loading