Skip to content
Open
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
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>"
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
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