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
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,46 @@ sdk = Sdk(
)
```

### Cloud POS order tips

For POS and Cloud POS integrations, you can send tip information as part of the order creation payload with `amount_details`. Amount values are expressed in the smallest currency unit, so this example sends a total order amount of EUR 1.20 with EUR 0.20 marked as tip.

```python
from multisafepay.api.paths.orders.request import OrderRequest
from multisafepay.api.paths.orders.request.components import (
AmountDetails,
Tip,
)


order_request = (
OrderRequest()
.add_type("redirect")
.add_order_id("cloud-pos-order-with-tip")
.add_description("Cloud POS order with tip")
.add_amount(120)
.add_currency("EUR")
.add_gateway_info({"terminal_id": "<terminal_id>"})
.add_amount_details(
AmountDetails().add_tip(Tip().add_amount(20)),
)
)
```

This serializes to:

```json
{
"amount_details": {
"tip": {
"amount": 20
}
}
}
```

See the full Cloud POS tip example in `examples/order_manager/cloud_pos_order_with_tip.py`.

### Development-only custom base URL override

By default, the SDK only targets:
Expand Down
63 changes: 63 additions & 0 deletions examples/order_manager/cloud_pos_order_with_tip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Create a Cloud POS order with tip information."""

import os
import time

from dotenv import load_dotenv

from multisafepay import Sdk
from multisafepay.api.paths.orders.request import OrderRequest
from multisafepay.api.paths.orders.request.components import (
AmountDetails,
Tip,
)
from multisafepay.client import ScopedCredentialResolver

load_dotenv()

DEFAULT_ACCOUNT_API_KEY = os.getenv("API_KEY", "")
TERMINAL_GROUP_DEFAULT_API_KEY = os.getenv(
"TERMINAL_GROUP_API_KEY_GROUP_DEFAULT", ""
)
CLOUD_POS_TERMINAL_GROUP_ID = os.getenv(
"CLOUD_POS_TERMINAL_GROUP_ID", "Default"
)
TERMINAL_ID = os.getenv("CLOUD_POS_TERMINAL_ID", "")
ORDER_AMOUNT = 120
TIP_AMOUNT = 20

if __name__ == "__main__":
credential_resolver = ScopedCredentialResolver(
default_api_key=DEFAULT_ACCOUNT_API_KEY,
terminal_group_api_keys={
CLOUD_POS_TERMINAL_GROUP_ID: TERMINAL_GROUP_DEFAULT_API_KEY,
},
)

multisafepay_sdk = Sdk(
is_production=False,
credential_resolver=credential_resolver,
)
order_manager = multisafepay_sdk.get_order_manager()

order_request = (
OrderRequest()
.add_type("redirect")
.add_order_id(f"cloud-pos-tip-{int(time.time())}")
.add_description("Cloud POS order with tip")
.add_amount(ORDER_AMOUNT)
.add_currency("EUR")
.add_gateway_info({"terminal_id": TERMINAL_ID})
.add_amount_details(
AmountDetails().add_tip(Tip().add_amount(TIP_AMOUNT)),
)
)

create_response = order_manager.create(
order_request,
terminal_group_id=CLOUD_POS_TERMINAL_GROUP_ID,
)
order = create_response.get_data()

print(f"Created Cloud POS order with tip: {order.order_id}")
print(order)
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Order request components for detailed order configuration and settings."""

from multisafepay.api.paths.orders.request.components.amount_details import (
AmountDetails,
)
from multisafepay.api.paths.orders.request.components.checkout_options import (
CheckoutOptions,
)
Expand All @@ -16,12 +19,15 @@
from multisafepay.api.paths.orders.request.components.second_chance import (
SecondChance,
)
from multisafepay.api.paths.orders.request.components.tip import Tip

__all__ = [
"AmountDetails",
"CheckoutOptions",
"CustomInfo",
"GoogleAnalytics",
"PaymentOptions",
"Plugin",
"SecondChance",
"Tip",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Amount details model for order request amount breakdowns."""

from typing import Optional

from multisafepay.api.paths.orders.request.components.tip import Tip
from multisafepay.model.request_model import RequestModel


class AmountDetails(RequestModel):
"""
Represents amount details for an order request.

Attributes
----------
tip (Optional[Tip]): The tip information.

"""

tip: Optional[Tip]

def add_tip(
self: "AmountDetails",
tip: Optional[Tip],
) -> "AmountDetails":
"""
Adds tip information to the amount details.

Parameters
----------
tip (Optional[Tip]): The tip information.

Returns
-------
AmountDetails: The updated AmountDetails object.

"""
self.tip = tip
return self
40 changes: 40 additions & 0 deletions src/multisafepay/api/paths/orders/request/components/tip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Tip model for order request amount details."""

from typing import Optional, Union

from multisafepay.model.request_model import RequestModel
from multisafepay.value_object.amount import Amount


class Tip(RequestModel):
"""
Represents tip information in order amount details.

Attributes
----------
amount (Optional[int]): The tip amount in the smallest currency unit.

"""

amount: Optional[int]

def add_amount(
self: "Tip",
amount: Optional[Union[Amount, int]],
) -> "Tip":
"""
Adds the tip amount.

Parameters
----------
amount (Optional[Amount | int]): The tip amount as an Amount object or integer.

Returns
-------
Tip: The updated Tip object.

"""
if isinstance(amount, int):
amount = Amount(amount=amount)
self.amount = amount.get() if amount is not None else None
return self
24 changes: 24 additions & 0 deletions src/multisafepay/api/paths/orders/request/order_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

from typing import Optional, Union

from multisafepay.api.paths.orders.request.components.amount_details import (
AmountDetails,
)
from multisafepay.api.paths.orders.request.components.checkout_options import (
CheckoutOptions,
)
Expand Down Expand Up @@ -67,6 +70,7 @@ class OrderRequest(RequestModel):
order_id (Optional[str]): The order ID.
currency (Optional[str]): The currency of the order.
amount (Optional[str]): The amount of the order.
amount_details (Optional[AmountDetails]): The amount details.
payment_options (Optional[PaymentOptions]): The payment options.
customer (Optional[Customer]): The customer.
delivery (Optional[Delivery]): The delivery information.
Expand All @@ -93,6 +97,7 @@ class OrderRequest(RequestModel):
order_id: Optional[str]
currency: Optional[str]
amount: Optional[int]
amount_details: Optional[AmountDetails]
capture: Optional[str]
payment_options: Optional[PaymentOptions]
customer: Optional[Customer]
Expand Down Expand Up @@ -248,6 +253,25 @@ def add_capture(
self.capture = capture
return self

def add_amount_details(
self: "OrderRequest",
amount_details: Optional[AmountDetails],
) -> "OrderRequest":
"""
Adds amount details to the order request.

Parameters
----------
amount_details (Optional[AmountDetails]): The amount details.

Returns
-------
OrderRequest: The updated OrderRequest object.

"""
self.amount_details = amount_details
return self

def add_payment_options(
self: "OrderRequest",
payment_options: Optional[PaymentOptions],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@

"""Manager class for Test Integration Order Manager Create.Py API operations."""

import json
from unittest.mock import MagicMock

from multisafepay.api.base.response.api_response import ApiResponse
from multisafepay.api.base.response.custom_api_response import (
CustomApiResponse,
)
from multisafepay.api.paths.orders.order_manager import OrderManager
from multisafepay.api.paths.orders.request.components.amount_details import (
AmountDetails,
)
from multisafepay.api.paths.orders.request.components.payment_options import (
PaymentOptions,
)
from multisafepay.api.paths.orders.request.components.tip import Tip
from multisafepay.api.paths.orders.request.order_request import OrderRequest
from multisafepay.api.paths.orders.response.order_response import Order
from multisafepay.api.shared.customer import Customer
Expand Down Expand Up @@ -113,7 +118,8 @@ def test_integration_order_manager_create_with_terminal_group_scope():
.add_type("direct")
.add_order_id("cloud-pos-order")
.add_currency("EUR")
.add_amount(100)
.add_amount(120)
.add_amount_details(AmountDetails().add_tip(Tip().add_amount(20)))
)

order_manager = OrderManager(client)
Expand All @@ -127,11 +133,17 @@ def test_integration_order_manager_create_with_terminal_group_scope():
assert response.get_data().order_id == "cloud-pos-order"

called_endpoint = client.create_post_request.call_args.args[0]
called_request_body = client.create_post_request.call_args.kwargs[
"request_body"
]
called_auth_scope = client.create_post_request.call_args.kwargs[
"auth_scope"
]

assert called_endpoint == "json/orders"
assert json.loads(called_request_body)["amount_details"] == {
"tip": {"amount": 20},
}
assert called_auth_scope == AuthScope(
scope=ScopedCredentialResolver.AUTH_SCOPE_TERMINAL_GROUP,
group_id="Default",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@

import pytest

from multisafepay.api.paths.orders.request.components.amount_details import (
AmountDetails,
)
from multisafepay.api.paths.orders.request.components.payment_options import (
PaymentOptions,
)
from multisafepay.api.paths.orders.request.components.plugin import (
Plugin,
)
from multisafepay.api.paths.orders.request.components.tip import Tip
from multisafepay.api.paths.orders.request.order_request import OrderRequest
from multisafepay.api.shared.cart.cart_item import CartItem
from multisafepay.api.shared.cart.shopping_cart import ShoppingCart
Expand Down Expand Up @@ -92,6 +96,7 @@ def test_initializes_order_request_correctly():
.add_cancel_url("https://multisafepay.com/cancel_url")
.add_close_window(True)
)
amount_details = AmountDetails().add_tip(Tip().add_amount(20))

order_request = (
OrderRequest()
Expand All @@ -105,6 +110,7 @@ def test_initializes_order_request_correctly():
.add_delivery(customer)
.add_plugin(plugin)
.add_payment_options(payment_options)
.add_amount_details(amount_details)
)

assert order_request.type == "redirect"
Expand All @@ -117,6 +123,10 @@ def test_initializes_order_request_correctly():
assert order_request.delivery == customer
assert order_request.plugin == plugin
assert order_request.payment_options == payment_options
assert order_request.amount_details == amount_details
assert order_request.to_dict()["amount_details"] == {
"tip": {"amount": 20},
}


def test_initializes_order_request_validate_amount_valid():
Expand Down
Loading
Loading