diff --git a/Readme.md b/Readme.md
index 3a41cc1..c7fc872 100644
--- a/Readme.md
+++ b/Readme.md
@@ -77,6 +77,13 @@ For more information how to obtain an API key visit the [following link](https:/
- [Get a list of timezones](#get-a-list-of-timezones)
- [Campaign languages](#campaign-languages)
- [Get a list of languages](#get-a-list-of-languages)
+ - [E-commerce](#e-commerce)
+ - [Shops](#shops)
+ - [Products and variants](#products-and-variants)
+ - [Categories](#categories)
+ - [Customers, orders and carts](#customers-orders-and-carts)
+ - [Imports](#imports)
+ - [Revenue reports and settings](#revenue-reports-and-settings)
## Installation
@@ -901,3 +908,113 @@ client = MailerLite.Client({
response = client.campaigns.languages()
```
+
+## E-commerce
+
+
+Every e-commerce call takes the shop ID first. The examples below assume:
+
+```python
+import mailerlite as MailerLite
+
+client = MailerLite.Client({
+ 'api_key': 'your-api-key'
+})
+```
+
+### Shops
+
+
+```python
+client.ecommerce_shops.list(limit=25)
+client.ecommerce_shops.get(1234)
+client.ecommerce_shops.create('My shop', 'https://shop.example.com', 'EUR')
+client.ecommerce_shops.update(1234, name='New name')
+client.ecommerce_shops.delete(1234)
+```
+
+### Products and variants
+
+
+```python
+client.ecommerce_products.list(1234, filter={'enabled': True}, sort='-created_at')
+client.ecommerce_products.get(1234, 5678)
+client.ecommerce_products.create(1234, 'Red T-shirt', price=19.99)
+client.ecommerce_products.update(1234, 5678, price=24.99)
+client.ecommerce_products.delete(1234, 5678)
+client.ecommerce_products.change_status(1234, 5678, False)
+client.ecommerce_products.assign_categories(1234, 5678, [91], replace=True)
+
+client.ecommerce_product_variants.list(1234, 5678)
+client.ecommerce_product_variants.sync(1234, 5678, [{'name': 'Large', 'attributes': {'size': 'L'}, 'resource_id': 'variant-l'}])
+client.ecommerce_product_variants.update(1234, 5678, 42, 'Extra large', {'size': 'XL'})
+client.ecommerce_product_variants.delete(1234, 5678, 42)
+```
+
+### Categories
+
+
+```python
+client.ecommerce_categories.list(1234)
+client.ecommerce_categories.get(1234, 91)
+client.ecommerce_categories.create(1234, 'Hats')
+client.ecommerce_categories.update(1234, 91, name='Caps')
+client.ecommerce_categories.delete(1234, 91)
+client.ecommerce_categories.products(1234, 91)
+client.ecommerce_categories.assign_product(1234, 91, 5678)
+client.ecommerce_categories.unassign_product(1234, 91, 5678)
+```
+
+### Customers, orders and carts
+
+
+```python
+client.ecommerce_customers.list(1234, filter={'email': 'customer@example.com'})
+client.ecommerce_customers.create(1234, 'customer@example.com', create_subscriber=True)
+client.ecommerce_customers.update(1234, 77, name='Jane')
+client.ecommerce_customers.delete(1234, 77)
+
+client.ecommerce_orders.list(1234, filter={'attributed': True, 'email_id': 555})
+client.ecommerce_orders.create(
+ 1234,
+ {'email': 'customer@example.com'},
+ {'items': [{'ecommerce_product_id': 5678, 'quantity': 1, 'price': 19.99}]},
+ status='complete',
+)
+client.ecommerce_orders.update(1234, 99, status='complete')
+client.ecommerce_orders.delete(1234, 99)
+
+client.ecommerce_carts.list(1234)
+client.ecommerce_carts.update(1234, 33, checkout_url='https://shop.example.com/cart')
+client.ecommerce_cart_items.list(1234, 33)
+client.ecommerce_cart_items.update(1234, 33, 8, quantity=2)
+client.ecommerce_cart_items.delete(1234, 33, 8)
+```
+
+### Imports
+
+
+```python
+client.ecommerce_imports.products(1234, [{'name': 'Red T-shirt', 'price': 19.99}])
+client.ecommerce_imports.categories(1234, [{'name': 'Hats'}])
+client.ecommerce_imports.customers(1234, [{'email': 'customer@example.com'}])
+```
+
+### Revenue reports and settings
+
+
+```python
+client.ecommerce_shops.stats(1234, '2026-10-01', '2026-10-31')
+
+client.campaigns.ecommerce_stats(1111, 1234)
+client.campaigns.ecommerce_orders(1111, 1234)
+
+client.subscribers.ecommerce_stats(2222, 1234)
+client.subscribers.ecommerce_orders(2222, 1234)
+
+client.automations.ecommerce_stats(3333)
+client.automations.email_ecommerce_stats(3333, 4444)
+client.automations.ecommerce_orders(3333, 'all_time')
+
+client.ecommerce_settings.get()
+```
diff --git a/mailerlite/__init__.py b/mailerlite/__init__.py
index 08810f2..447d738 100644
--- a/mailerlite/__init__.py
+++ b/mailerlite/__init__.py
@@ -8,6 +8,16 @@
# import all api files into api package
from mailerlite.sdk.campaigns import Campaigns
+from mailerlite.sdk.ecommerce_carts import EcommerceCarts
+from mailerlite.sdk.ecommerce_cart_items import EcommerceCartItems
+from mailerlite.sdk.ecommerce_categories import EcommerceCategories
+from mailerlite.sdk.ecommerce_customers import EcommerceCustomers
+from mailerlite.sdk.ecommerce_imports import EcommerceImports
+from mailerlite.sdk.ecommerce_orders import EcommerceOrders
+from mailerlite.sdk.ecommerce_product_variants import EcommerceProductVariants
+from mailerlite.sdk.ecommerce_products import EcommerceProducts
+from mailerlite.sdk.ecommerce_settings import EcommerceSettings
+from mailerlite.sdk.ecommerce_shops import EcommerceShops
from mailerlite.sdk.fields import Fields
from mailerlite.sdk.forms import Forms
from mailerlite.sdk.groups import Groups
@@ -33,3 +43,13 @@ def __init__(self, config={}):
self.webhooks = Webhooks(self.api_client)
self.automations = Automations(self.api_client)
self.batches = Batches(self.api_client)
+ self.ecommerce_shops = EcommerceShops(self.api_client)
+ self.ecommerce_products = EcommerceProducts(self.api_client)
+ self.ecommerce_product_variants = EcommerceProductVariants(self.api_client)
+ self.ecommerce_categories = EcommerceCategories(self.api_client)
+ self.ecommerce_customers = EcommerceCustomers(self.api_client)
+ self.ecommerce_orders = EcommerceOrders(self.api_client)
+ self.ecommerce_carts = EcommerceCarts(self.api_client)
+ self.ecommerce_cart_items = EcommerceCartItems(self.api_client)
+ self.ecommerce_imports = EcommerceImports(self.api_client)
+ self.ecommerce_settings = EcommerceSettings(self.api_client)
diff --git a/mailerlite/sdk/automations.py b/mailerlite/sdk/automations.py
index 94b33a0..7dce469 100644
--- a/mailerlite/sdk/automations.py
+++ b/mailerlite/sdk/automations.py
@@ -82,3 +82,101 @@ def activity(self, automation_id, **kwargs):
f"{self.base_api_url}/{automation_id}/activity",
query_params,
).json()
+
+ def ecommerce_stats(self, automation_id, **kwargs):
+ """
+ Get the orders and revenue an automation earned
+ Ref: https://developers.mailerlite.com/docs/automations.html
+
+ :param automation_id: int Automation ID
+ :param **kwargs: You can pass additional arguments - shop_id, start_date or end_date
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(automation_id, int):
+ raise TypeError(
+ f"`automation_id` type is not valid. Expected `int`, got {type(automation_id)}."
+ )
+
+ query_params = {}
+ for key, val in kwargs.items():
+ if key not in ["shop_id", "start_date", "end_date"]:
+ raise TypeError("Got an unknown argument '%s'" % key)
+ query_params[key] = val
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{automation_id}/monthly-totals/ecommerce",
+ query_params,
+ ).json()
+
+ def email_ecommerce_stats(self, automation_id, email_id, **kwargs):
+ """
+ Get the orders and revenue one automation email earned
+ Ref: https://developers.mailerlite.com/docs/automations.html
+
+ :param automation_id: int Automation ID
+ :param email_id: int Email ID
+ :param **kwargs: You can pass additional arguments - shop_id, start_date or end_date
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(automation_id, int):
+ raise TypeError(
+ f"`automation_id` type is not valid. Expected `int`, got {type(automation_id)}."
+ )
+
+ if not isinstance(email_id, int):
+ raise TypeError(
+ f"`email_id` type is not valid. Expected `int`, got {type(email_id)}."
+ )
+
+ query_params = {}
+ for key, val in kwargs.items():
+ if key not in ["shop_id", "start_date", "end_date"]:
+ raise TypeError("Got an unknown argument '%s'" % key)
+ query_params[key] = val
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{automation_id}/emails/{email_id}/ecommerce-overview-stats",
+ query_params,
+ ).json()
+
+ def ecommerce_orders(self, automation_id, time_frame, **kwargs):
+ """
+ List the orders attributed to an automation
+ Ref: https://developers.mailerlite.com/docs/automations.html
+
+ :param automation_id: int Automation ID
+ :param time_frame: str week, month, two_months, three_months, six_months, year, last_year, all_time or custom
+ :param **kwargs: You can pass additional arguments - time_frame_start, time_frame_end, ecommerce_shop_id, page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(automation_id, int):
+ raise TypeError(
+ f"`automation_id` type is not valid. Expected `int`, got {type(automation_id)}."
+ )
+
+ available_params = [
+ "time_frame_start",
+ "time_frame_end",
+ "ecommerce_shop_id",
+ "page",
+ "limit",
+ ]
+ query_params = {"time_frame": time_frame}
+ for key, val in kwargs.items():
+ if key not in available_params:
+ raise TypeError("Got an unknown argument '%s'" % key)
+ query_params[key] = val
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{automation_id}/order-activity",
+ query_params,
+ ).json()
diff --git a/mailerlite/sdk/campaigns.py b/mailerlite/sdk/campaigns.py
index e1e3a39..92954a1 100644
--- a/mailerlite/sdk/campaigns.py
+++ b/mailerlite/sdk/campaigns.py
@@ -192,3 +192,54 @@ def languages(self):
"""
return self.api_client.request("GET", f"{self.base_api_url}/languages").json()
+
+ def ecommerce_stats(self, campaign_id, shop_id):
+ """
+ Get the orders and revenue a campaign earned in one shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param campaign_id: int Campaign ID
+ :param shop_id: int Shop ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(campaign_id, int):
+ raise TypeError(
+ f"`campaign_id` type is not valid. Expected `int`, got {type(campaign_id)}."
+ )
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{campaign_id}/reports/ecommerce",
+ {"shop_id": shop_id},
+ ).json()
+
+ def ecommerce_orders(self, campaign_id, shop_id, **kwargs):
+ """
+ List the orders a campaign has in one shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param campaign_id: int Campaign ID
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(campaign_id, int):
+ raise TypeError(
+ f"`campaign_id` type is not valid. Expected `int`, got {type(campaign_id)}."
+ )
+
+ query_params = {"shop_id": shop_id}
+ for key, val in kwargs.items():
+ if key not in ["page", "limit"]:
+ raise TypeError("Got an unknown argument '%s'" % key)
+ query_params[key] = val
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{campaign_id}/reports/ecommerce/orders",
+ query_params,
+ ).json()
diff --git a/mailerlite/sdk/ecommerce_cart_items.py b/mailerlite/sdk/ecommerce_cart_items.py
new file mode 100644
index 0000000..15e05c5
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_cart_items.py
@@ -0,0 +1,101 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceCartItems(EcommerceResource):
+ def _items_url(self, shop_id, cart_id, path=""):
+ self._check_id("cart_id", cart_id)
+
+ return self._shop_url(shop_id, f"/carts/{cart_id}/items{path}")
+
+ def list(self, shop_id, cart_id, **kwargs):
+ """
+ List a cart's items
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart-items
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :param **kwargs: You can pass additional arguments - page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit"])
+
+ return self.api_client.request(
+ "GET", self._items_url(shop_id, cart_id), query_params
+ ).json()
+
+ def get(self, shop_id, cart_id, item_id):
+ """
+ Get a cart item
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart-items
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :param item_id: int Cart item ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("item_id", item_id)
+
+ return self.api_client.request(
+ "GET", self._items_url(shop_id, cart_id, f"/{item_id}")
+ ).json()
+
+ def create(self, shop_id, cart_id, **kwargs):
+ """
+ Add an item to a cart
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart-items
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :param **kwargs: The cart item fields, e.g. ecommerce_product_id, quantity
+ :return: JSON array
+ :rtype: dict
+ """
+
+ return self.api_client.request(
+ "POST", self._items_url(shop_id, cart_id), body=kwargs
+ ).json()
+
+ def update(self, shop_id, cart_id, item_id, **kwargs):
+ """
+ Update a cart item
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart-items
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :param item_id: int Cart item ID
+ :param **kwargs: The cart item fields to update
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("item_id", item_id)
+
+ return self.api_client.request(
+ "PUT", self._items_url(shop_id, cart_id, f"/{item_id}"), body=kwargs
+ ).json()
+
+ def delete(self, shop_id, cart_id, item_id):
+ """
+ Delete a cart item
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart-items
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :param item_id: int Cart item ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("item_id", item_id)
+
+ self.api_client.request(
+ "DELETE", self._items_url(shop_id, cart_id, f"/{item_id}")
+ )
+
+ return True
diff --git a/mailerlite/sdk/ecommerce_carts.py b/mailerlite/sdk/ecommerce_carts.py
new file mode 100644
index 0000000..2bbeb94
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_carts.py
@@ -0,0 +1,57 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceCarts(EcommerceResource):
+ def list(self, shop_id, **kwargs):
+ """
+ List carts
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart
+
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit"])
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, "/carts"), query_params
+ ).json()
+
+ def get(self, shop_id, cart_id):
+ """
+ Get a cart
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("cart_id", cart_id)
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, f"/carts/{cart_id}")
+ ).json()
+
+ def update(self, shop_id, cart_id, **kwargs):
+ """
+ Update a cart
+ Ref: https://developers.mailerlite.com/docs/ecommerce/cart
+
+ :param shop_id: int Shop ID
+ :param cart_id: int Cart ID
+ :param **kwargs: The cart fields to update, e.g. checkout_url, cart_total
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("cart_id", cart_id)
+
+ return self.api_client.request(
+ "PUT", self._shop_url(shop_id, f"/carts/{cart_id}"), body=kwargs
+ ).json()
diff --git a/mailerlite/sdk/ecommerce_categories.py b/mailerlite/sdk/ecommerce_categories.py
new file mode 100644
index 0000000..04749ab
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_categories.py
@@ -0,0 +1,157 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceCategories(EcommerceResource):
+ def list(self, shop_id, **kwargs):
+ """
+ List categories
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories
+
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page, limit, filter
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit", "filter"])
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, "/categories"), query_params
+ ).json()
+
+ def get(self, shop_id, category_id):
+ """
+ Get a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories
+
+ :param shop_id: int Shop ID
+ :param category_id: int Category ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("category_id", category_id)
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, f"/categories/{category_id}")
+ ).json()
+
+ def create(self, shop_id, name, **kwargs):
+ """
+ Create or update a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories
+
+ :param shop_id: int Shop ID
+ :param **kwargs: Any other category fields
+ :return: JSON array
+ :rtype: dict
+ """
+
+ body_params = {"name": name, **kwargs}
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/categories"), body=body_params
+ ).json()
+
+ def update(self, shop_id, category_id, **kwargs):
+ """
+ Update a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories
+
+ :param shop_id: int Shop ID
+ :param category_id: int Category ID
+ :param **kwargs: The category fields to update
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("category_id", category_id)
+
+ return self.api_client.request(
+ "PUT", self._shop_url(shop_id, f"/categories/{category_id}"), body=kwargs
+ ).json()
+
+ def delete(self, shop_id, category_id):
+ """
+ Delete a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories
+
+ :param shop_id: int Shop ID
+ :param category_id: int Category ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("category_id", category_id)
+
+ self.api_client.request(
+ "DELETE", self._shop_url(shop_id, f"/categories/{category_id}")
+ )
+
+ return True
+
+ def products(self, shop_id, category_id, **kwargs):
+ """
+ List the products in a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories-products
+
+ :param shop_id: int Shop ID
+ :param category_id: int Category ID
+ :param **kwargs: You can pass additional arguments - page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("category_id", category_id)
+ query_params = self._query_params(kwargs, ["page", "limit"])
+
+ return self.api_client.request(
+ "GET",
+ self._shop_url(shop_id, f"/categories/{category_id}/products"),
+ query_params,
+ ).json()
+
+ def assign_product(self, shop_id, category_id, product_id):
+ """
+ Assign a product to a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories-products
+
+ :param shop_id: int Shop ID
+ :param category_id: int Category ID
+ :param product_id: int Product ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("category_id", category_id)
+ self._check_id("product_id", product_id)
+
+ return self.api_client.request(
+ "PUT",
+ self._shop_url(shop_id, f"/categories/{category_id}/products/{product_id}"),
+ body={},
+ ).json()
+
+ def unassign_product(self, shop_id, category_id, product_id):
+ """
+ Unassign a product from a category
+ Ref: https://developers.mailerlite.com/docs/ecommerce/categories-products
+
+ :param shop_id: int Shop ID
+ :param category_id: int Category ID
+ :param product_id: int Product ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("category_id", category_id)
+ self._check_id("product_id", product_id)
+
+ self.api_client.request(
+ "DELETE",
+ self._shop_url(shop_id, f"/categories/{category_id}/products/{product_id}"),
+ )
+
+ return True
diff --git a/mailerlite/sdk/ecommerce_customers.py b/mailerlite/sdk/ecommerce_customers.py
new file mode 100644
index 0000000..aa8f2c6
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_customers.py
@@ -0,0 +1,93 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceCustomers(EcommerceResource):
+ def list(self, shop_id, **kwargs):
+ """
+ List customers
+ Ref: https://developers.mailerlite.com/docs/ecommerce/customers
+
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page, limit, sort, filter
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit", "sort", "filter"])
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, "/customers"), query_params
+ ).json()
+
+ def get(self, shop_id, customer_id):
+ """
+ Get a customer
+ Ref: https://developers.mailerlite.com/docs/ecommerce/customers
+
+ :param shop_id: int Shop ID
+ :param customer_id: int Customer ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("customer_id", customer_id)
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, f"/customers/{customer_id}")
+ ).json()
+
+ def create(self, shop_id, email, **kwargs):
+ """
+ Create or update a customer
+ Ref: https://developers.mailerlite.com/docs/ecommerce/customers
+
+ :param shop_id: int Shop ID
+ :param **kwargs: Any other customer fields
+ :return: JSON array
+ :rtype: dict
+ """
+
+ body_params = {"email": email, **kwargs}
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/customers"), body=body_params
+ ).json()
+
+ def update(self, shop_id, customer_id, **kwargs):
+ """
+ Update a customer
+ Ref: https://developers.mailerlite.com/docs/ecommerce/customers
+
+ :param shop_id: int Shop ID
+ :param customer_id: int Customer ID
+ :param **kwargs: The customer fields to update
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("customer_id", customer_id)
+
+ return self.api_client.request(
+ "PUT", self._shop_url(shop_id, f"/customers/{customer_id}"), body=kwargs
+ ).json()
+
+ def delete(self, shop_id, customer_id):
+ """
+ Delete a customer
+ Ref: https://developers.mailerlite.com/docs/ecommerce/customers
+
+ :param shop_id: int Shop ID
+ :param customer_id: int Customer ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("customer_id", customer_id)
+
+ self.api_client.request(
+ "DELETE", self._shop_url(shop_id, f"/customers/{customer_id}")
+ )
+
+ return True
diff --git a/mailerlite/sdk/ecommerce_imports.py b/mailerlite/sdk/ecommerce_imports.py
new file mode 100644
index 0000000..764cd91
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_imports.py
@@ -0,0 +1,85 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceImports(EcommerceResource):
+ def products(self, shop_id, products):
+ """
+ Import products
+ Ref: https://developers.mailerlite.com/docs/ecommerce/import
+
+ :param shop_id: int Shop ID
+ :param products: list The products to import
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if type(products) is not list:
+ raise TypeError(
+ f"`products` type is not valid. Expected `list`, got {type(products)}."
+ )
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/products/import"), body=products
+ ).json()
+
+ def categories(self, shop_id, categories):
+ """
+ Import categories
+ Ref: https://developers.mailerlite.com/docs/ecommerce/import
+
+ :param shop_id: int Shop ID
+ :param categories: list The categories to import
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if type(categories) is not list:
+ raise TypeError(
+ f"`categories` type is not valid. Expected `list`, got {type(categories)}."
+ )
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/categories/import"), body=categories
+ ).json()
+
+ def customers(self, shop_id, customers):
+ """
+ Import customers
+ Ref: https://developers.mailerlite.com/docs/ecommerce/import
+
+ :param shop_id: int Shop ID
+ :param customers: list The customers to import
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if type(customers) is not list:
+ raise TypeError(
+ f"`customers` type is not valid. Expected `list`, got {type(customers)}."
+ )
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/customers/import"), body=customers
+ ).json()
+
+ def orders(self, shop_id, orders):
+ """
+ Import orders
+ Ref: https://developers.mailerlite.com/docs/ecommerce/import
+
+ :param shop_id: int Shop ID
+ :param orders: list The orders to import
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if type(orders) is not list:
+ raise TypeError(
+ f"`orders` type is not valid. Expected `list`, got {type(orders)}."
+ )
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/orders/import"), body={"orders": orders}
+ ).json()
diff --git a/mailerlite/sdk/ecommerce_orders.py b/mailerlite/sdk/ecommerce_orders.py
new file mode 100644
index 0000000..9c76beb
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_orders.py
@@ -0,0 +1,93 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceOrders(EcommerceResource):
+ def list(self, shop_id, **kwargs):
+ """
+ List orders
+ Ref: https://developers.mailerlite.com/docs/ecommerce/orders
+
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page, limit, sort, filter
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit", "sort", "filter"])
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, "/orders"), query_params
+ ).json()
+
+ def get(self, shop_id, order_id):
+ """
+ Get a order
+ Ref: https://developers.mailerlite.com/docs/ecommerce/orders
+
+ :param shop_id: int Shop ID
+ :param order_id: int Order ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("order_id", order_id)
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, f"/orders/{order_id}")
+ ).json()
+
+ def create(self, shop_id, customer, cart, **kwargs):
+ """
+ Create or update a order
+ Ref: https://developers.mailerlite.com/docs/ecommerce/orders
+
+ :param shop_id: int Shop ID
+ :param **kwargs: Any other order fields
+ :return: JSON array
+ :rtype: dict
+ """
+
+ body_params = {"customer": customer, "cart": cart, **kwargs}
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/orders"), body=body_params
+ ).json()
+
+ def update(self, shop_id, order_id, **kwargs):
+ """
+ Update a order
+ Ref: https://developers.mailerlite.com/docs/ecommerce/orders
+
+ :param shop_id: int Shop ID
+ :param order_id: int Order ID
+ :param **kwargs: The order fields to update
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("order_id", order_id)
+
+ return self.api_client.request(
+ "PUT", self._shop_url(shop_id, f"/orders/{order_id}"), body=kwargs
+ ).json()
+
+ def delete(self, shop_id, order_id):
+ """
+ Delete a order
+ Ref: https://developers.mailerlite.com/docs/ecommerce/orders
+
+ :param shop_id: int Shop ID
+ :param order_id: int Order ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("order_id", order_id)
+
+ self.api_client.request(
+ "DELETE", self._shop_url(shop_id, f"/orders/{order_id}")
+ )
+
+ return True
diff --git a/mailerlite/sdk/ecommerce_product_variants.py b/mailerlite/sdk/ecommerce_product_variants.py
new file mode 100644
index 0000000..bd30809
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_product_variants.py
@@ -0,0 +1,95 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceProductVariants(EcommerceResource):
+ def list(self, shop_id, product_id):
+ """
+ List a product's variants
+ Ref: https://developers.mailerlite.com/docs/ecommerce/product-variants
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, f"/products/{product_id}/variants")
+ ).json()
+
+ def sync(self, shop_id, product_id, variants):
+ """
+ Create or update a product's variants from a list
+ Ref: https://developers.mailerlite.com/docs/ecommerce/product-variants
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :param variants: list Variants, each with at least name and attributes
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+
+ if type(variants) is not list:
+ raise TypeError(
+ f"`variants` type is not valid. Expected `list`, got {type(variants)}."
+ )
+
+ return self.api_client.request(
+ "POST",
+ self._shop_url(shop_id, f"/products/{product_id}/variants"),
+ body=variants,
+ ).json()
+
+ def update(self, shop_id, product_id, variant_id, name, attributes, **kwargs):
+ """
+ Update a product variant
+ Ref: https://developers.mailerlite.com/docs/ecommerce/product-variants
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :param variant_id: int Variant ID
+ :param name: str Variant name
+ :param attributes: dict Attributes that define the variant, e.g. {"size": "L"}
+ :param **kwargs: Any other variant fields, e.g. price_type, price
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+ self._check_id("variant_id", variant_id)
+
+ body_params = {"name": name, "attributes": attributes, **kwargs}
+
+ return self.api_client.request(
+ "PUT",
+ self._shop_url(shop_id, f"/products/{product_id}/variants/{variant_id}"),
+ body=body_params,
+ ).json()
+
+ def delete(self, shop_id, product_id, variant_id):
+ """
+ Delete a product variant
+ Ref: https://developers.mailerlite.com/docs/ecommerce/product-variants
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :param variant_id: int Variant ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("product_id", product_id)
+ self._check_id("variant_id", variant_id)
+
+ self.api_client.request(
+ "DELETE",
+ self._shop_url(shop_id, f"/products/{product_id}/variants/{variant_id}"),
+ )
+
+ return True
diff --git a/mailerlite/sdk/ecommerce_products.py b/mailerlite/sdk/ecommerce_products.py
new file mode 100644
index 0000000..4da0833
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_products.py
@@ -0,0 +1,139 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceProducts(EcommerceResource):
+ def list(self, shop_id, **kwargs):
+ """
+ List products
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page, limit, sort, filter
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit", "sort", "filter"])
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, "/products"), query_params
+ ).json()
+
+ def get(self, shop_id, product_id):
+ """
+ Get a product
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+
+ return self.api_client.request(
+ "GET", self._shop_url(shop_id, f"/products/{product_id}")
+ ).json()
+
+ def create(self, shop_id, name, **kwargs):
+ """
+ Create or update a product
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param **kwargs: Any other product fields
+ :return: JSON array
+ :rtype: dict
+ """
+
+ body_params = {"name": name, **kwargs}
+
+ return self.api_client.request(
+ "POST", self._shop_url(shop_id, "/products"), body=body_params
+ ).json()
+
+ def update(self, shop_id, product_id, **kwargs):
+ """
+ Update a product
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :param **kwargs: The product fields to update
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+
+ return self.api_client.request(
+ "PUT", self._shop_url(shop_id, f"/products/{product_id}"), body=kwargs
+ ).json()
+
+ def delete(self, shop_id, product_id):
+ """
+ Delete a product
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self._check_id("product_id", product_id)
+
+ self.api_client.request(
+ "DELETE", self._shop_url(shop_id, f"/products/{product_id}")
+ )
+
+ return True
+
+ def change_status(self, shop_id, product_id, enabled):
+ """
+ Enable or disable a product
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :param enabled: bool Whether the product is enabled
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+
+ return self.api_client.request(
+ "PUT",
+ self._shop_url(shop_id, f"/products/{product_id}/change-status"),
+ body={"status": enabled},
+ ).json()
+
+ def assign_categories(self, shop_id, product_id, categories, replace=False):
+ """
+ Assign categories to a product
+ Ref: https://developers.mailerlite.com/docs/ecommerce/products
+
+ :param shop_id: int Shop ID
+ :param product_id: int Product ID
+ :param categories: list Category IDs
+ :param replace: bool Also remove the categories that are not listed
+ :return: JSON array
+ :rtype: dict
+ """
+
+ self._check_id("product_id", product_id)
+
+ if type(categories) is not list:
+ raise TypeError(
+ f"`categories` type is not valid. Expected `list`, got {type(categories)}."
+ )
+
+ return self.api_client.request(
+ "PUT",
+ self._shop_url(shop_id, f"/products/{product_id}/categories/multiple"),
+ body={"categories": categories, "replace": replace},
+ ).json()
diff --git a/mailerlite/sdk/ecommerce_resource.py b/mailerlite/sdk/ecommerce_resource.py
new file mode 100644
index 0000000..1e00b17
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_resource.py
@@ -0,0 +1,35 @@
+from __future__ import absolute_import
+
+
+class EcommerceResource(object):
+ # E-commerce base API uri
+ base_api_url = "api/ecommerce/shops"
+
+ def __init__(self, api_client):
+ self.api_client = api_client
+
+ def _shop_url(self, shop_id, path=""):
+ self._check_id("shop_id", shop_id)
+
+ return f"{self.base_api_url}/{shop_id}{path}"
+
+ @staticmethod
+ def _check_id(name, value):
+ if not isinstance(value, int):
+ raise TypeError(
+ f"`{name}` type is not valid. Expected `int`, got {type(value)}."
+ )
+
+ @staticmethod
+ def _query_params(kwargs, available_params):
+ query_params = {}
+ for key, val in kwargs.items():
+ if key not in available_params:
+ raise TypeError("Got an unknown argument '%s'" % key)
+ if key == "filter":
+ for filter_key, filter_value in val.items():
+ query_params[f"filter[{filter_key}]"] = filter_value
+ else:
+ query_params[key] = val
+
+ return query_params
diff --git a/mailerlite/sdk/ecommerce_settings.py b/mailerlite/sdk/ecommerce_settings.py
new file mode 100644
index 0000000..7375873
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_settings.py
@@ -0,0 +1,20 @@
+from __future__ import absolute_import
+
+
+class EcommerceSettings(object):
+ # E-commerce settings base API uri
+ base_api_url = "api/ecommerce/settings"
+
+ def __init__(self, api_client):
+ self.api_client = api_client
+
+ def get(self):
+ """
+ Get the e-commerce tracking settings
+ Ref: https://developers.mailerlite.com/docs/ecommerce/settings
+
+ :return: JSON array
+ :rtype: dict
+ """
+
+ return self.api_client.request("GET", self.base_api_url).json()
diff --git a/mailerlite/sdk/ecommerce_shops.py b/mailerlite/sdk/ecommerce_shops.py
new file mode 100644
index 0000000..0247a89
--- /dev/null
+++ b/mailerlite/sdk/ecommerce_shops.py
@@ -0,0 +1,97 @@
+from __future__ import absolute_import
+
+from mailerlite.sdk.ecommerce_resource import EcommerceResource
+
+
+class EcommerceShops(EcommerceResource):
+ def list(self, **kwargs):
+ """
+ List shops
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param **kwargs: You can pass additional arguments - page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ query_params = self._query_params(kwargs, ["page", "limit"])
+
+ return self.api_client.request("GET", self.base_api_url, query_params).json()
+
+ def get(self, shop_id):
+ """
+ Get a shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param shop_id: int Shop ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ return self.api_client.request("GET", self._shop_url(shop_id)).json()
+
+ def create(self, name, url, currency, **kwargs):
+ """
+ Create a shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param name: str Shop name
+ :param url: str Shop URL
+ :param currency: str ISO 4217 currency code, e.g. EUR
+ :param **kwargs: Any other shop fields, e.g. platform, group_id, enabled
+ :return: JSON array
+ :rtype: dict
+ """
+
+ body_params = {"name": name, "url": url, "currency": currency, **kwargs}
+
+ return self.api_client.request(
+ "POST", self.base_api_url, body=body_params
+ ).json()
+
+ def update(self, shop_id, **kwargs):
+ """
+ Update a shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param shop_id: int Shop ID
+ :param **kwargs: The shop fields to update
+ :return: JSON array
+ :rtype: dict
+ """
+
+ return self.api_client.request(
+ "PUT", self._shop_url(shop_id), body=kwargs
+ ).json()
+
+ def delete(self, shop_id):
+ """
+ Delete a shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param shop_id: int Shop ID
+ :return: `true` if action was successful
+ :rtype: bool
+ """
+
+ self.api_client.request("DELETE", self._shop_url(shop_id))
+
+ return True
+
+ def stats(self, shop_id, start_date, end_date):
+ """
+ Get a shop's attributed orders and revenue over a period of up to one year
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param shop_id: int Shop ID
+ :param start_date: str First day, e.g. 2026-01-01
+ :param end_date: str Last day, included
+ :return: JSON array
+ :rtype: dict
+ """
+
+ return self.api_client.request(
+ "GET",
+ self._shop_url(shop_id, "/reports/stats"),
+ {"start_date": start_date, "end_date": end_date},
+ ).json()
diff --git a/mailerlite/sdk/subscribers.py b/mailerlite/sdk/subscribers.py
index 9281eaf..a8877d4 100644
--- a/mailerlite/sdk/subscribers.py
+++ b/mailerlite/sdk/subscribers.py
@@ -308,3 +308,54 @@ def forget(self, subscriber_id):
self.api_client.request("POST", f"{self.base_api_url}/{subscriber_id}/forget")
return True
+
+ def ecommerce_stats(self, subscriber_id, shop_id):
+ """
+ Get the orders and revenue a subscriber earned in one shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param subscriber_id: int Subscriber ID
+ :param shop_id: int Shop ID
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(subscriber_id, int):
+ raise TypeError(
+ f"`subscriber_id` type is not valid. Expected `int`, got {type(subscriber_id)}."
+ )
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{subscriber_id}/reports/ecommerce",
+ {"shop_id": shop_id},
+ ).json()
+
+ def ecommerce_orders(self, subscriber_id, shop_id, **kwargs):
+ """
+ List the orders a subscriber has in one shop
+ Ref: https://developers.mailerlite.com/docs/ecommerce/shops
+
+ :param subscriber_id: int Subscriber ID
+ :param shop_id: int Shop ID
+ :param **kwargs: You can pass additional arguments - page or limit
+ :return: JSON array
+ :rtype: dict
+ """
+
+ if not isinstance(subscriber_id, int):
+ raise TypeError(
+ f"`subscriber_id` type is not valid. Expected `int`, got {type(subscriber_id)}."
+ )
+
+ query_params = {"shop_id": shop_id}
+ for key, val in kwargs.items():
+ if key not in ["page", "limit"]:
+ raise TypeError("Got an unknown argument '%s'" % key)
+ query_params[key] = val
+
+ return self.api_client.request(
+ "GET",
+ f"{self.base_api_url}/{subscriber_id}/reports/ecommerce/orders",
+ query_params,
+ ).json()
diff --git a/tests/ecommerce_test.py b/tests/ecommerce_test.py
new file mode 100644
index 0000000..6a86c80
--- /dev/null
+++ b/tests/ecommerce_test.py
@@ -0,0 +1,395 @@
+import json
+
+import mailerlite as MailerLite
+import pytest
+
+API = "https://connect.mailerlite.com/api"
+SHOP = f"{API}/ecommerce/shops/1"
+VARIANT = {"name": "Large", "attributes": {"size": "L"}}
+
+REQUESTS = [
+ (
+ lambda c: c.ecommerce_shops.list(limit=10),
+ "get",
+ f"{API}/ecommerce/shops",
+ {"limit": 10},
+ None,
+ ),
+ (lambda c: c.ecommerce_shops.get(1), "get", SHOP, None, None),
+ (
+ lambda c: c.ecommerce_shops.create("Shop", "https://shop.test", "EUR"),
+ "post",
+ f"{API}/ecommerce/shops",
+ None,
+ {"name": "Shop", "url": "https://shop.test", "currency": "EUR"},
+ ),
+ (
+ lambda c: c.ecommerce_shops.update(1, name="New"),
+ "put",
+ SHOP,
+ None,
+ {"name": "New"},
+ ),
+ (lambda c: c.ecommerce_shops.delete(1), "delete", SHOP, None, None),
+ (
+ lambda c: c.ecommerce_shops.stats(1, "2026-10-01", "2026-10-31"),
+ "get",
+ f"{SHOP}/reports/stats",
+ {"start_date": "2026-10-01", "end_date": "2026-10-31"},
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_settings.get(),
+ "get",
+ f"{API}/ecommerce/settings",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_products.list(1, filter={"enabled": True}, sort="-name"),
+ "get",
+ f"{SHOP}/products",
+ {"filter[enabled]": True, "sort": "-name"},
+ None,
+ ),
+ (lambda c: c.ecommerce_products.get(1, 7), "get", f"{SHOP}/products/7", None, None),
+ (
+ lambda c: c.ecommerce_products.create(1, "Hat", price=19.99),
+ "post",
+ f"{SHOP}/products",
+ None,
+ {"name": "Hat", "price": 19.99},
+ ),
+ (
+ lambda c: c.ecommerce_products.update(1, 7, name="Cap"),
+ "put",
+ f"{SHOP}/products/7",
+ None,
+ {"name": "Cap"},
+ ),
+ (
+ lambda c: c.ecommerce_products.delete(1, 7),
+ "delete",
+ f"{SHOP}/products/7",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_products.change_status(1, 7, False),
+ "put",
+ f"{SHOP}/products/7/change-status",
+ None,
+ {"status": False},
+ ),
+ (
+ lambda c: c.ecommerce_products.assign_categories(1, 7, [5], replace=True),
+ "put",
+ f"{SHOP}/products/7/categories/multiple",
+ None,
+ {"categories": [5], "replace": True},
+ ),
+ (
+ lambda c: c.ecommerce_product_variants.list(1, 7),
+ "get",
+ f"{SHOP}/products/7/variants",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_product_variants.sync(1, 7, [VARIANT]),
+ "post",
+ f"{SHOP}/products/7/variants",
+ None,
+ [VARIANT],
+ ),
+ (
+ lambda c: c.ecommerce_product_variants.update(1, 7, 3, "Large", {"size": "L"}),
+ "put",
+ f"{SHOP}/products/7/variants/3",
+ None,
+ VARIANT,
+ ),
+ (
+ lambda c: c.ecommerce_product_variants.delete(1, 7, 3),
+ "delete",
+ f"{SHOP}/products/7/variants/3",
+ None,
+ None,
+ ),
+ (lambda c: c.ecommerce_categories.list(1), "get", f"{SHOP}/categories", {}, None),
+ (
+ lambda c: c.ecommerce_categories.get(1, 5),
+ "get",
+ f"{SHOP}/categories/5",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_categories.create(1, "Hats"),
+ "post",
+ f"{SHOP}/categories",
+ None,
+ {"name": "Hats"},
+ ),
+ (
+ lambda c: c.ecommerce_categories.update(1, 5, name="Caps"),
+ "put",
+ f"{SHOP}/categories/5",
+ None,
+ {"name": "Caps"},
+ ),
+ (
+ lambda c: c.ecommerce_categories.delete(1, 5),
+ "delete",
+ f"{SHOP}/categories/5",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_categories.products(1, 5),
+ "get",
+ f"{SHOP}/categories/5/products",
+ {},
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_categories.assign_product(1, 5, 7),
+ "put",
+ f"{SHOP}/categories/5/products/7",
+ None,
+ {},
+ ),
+ (
+ lambda c: c.ecommerce_categories.unassign_product(1, 5, 7),
+ "delete",
+ f"{SHOP}/categories/5/products/7",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_customers.list(1, filter={"email": "a@b.test"}),
+ "get",
+ f"{SHOP}/customers",
+ {"filter[email]": "a@b.test"},
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_customers.get(1, 3),
+ "get",
+ f"{SHOP}/customers/3",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_customers.create(1, "a@b.test"),
+ "post",
+ f"{SHOP}/customers",
+ None,
+ {"email": "a@b.test"},
+ ),
+ (
+ lambda c: c.ecommerce_customers.update(1, 3, name="Jane"),
+ "put",
+ f"{SHOP}/customers/3",
+ None,
+ {"name": "Jane"},
+ ),
+ (
+ lambda c: c.ecommerce_customers.delete(1, 3),
+ "delete",
+ f"{SHOP}/customers/3",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_orders.list(
+ 1, filter={"attributed": True, "email_id": 55}
+ ),
+ "get",
+ f"{SHOP}/orders",
+ {"filter[attributed]": True, "filter[email_id]": 55},
+ None,
+ ),
+ (lambda c: c.ecommerce_orders.get(1, 9), "get", f"{SHOP}/orders/9", None, None),
+ (
+ lambda c: c.ecommerce_orders.create(
+ 1, {"email": "a@b.test"}, {"items": [{"ecommerce_product_id": 7}]}
+ ),
+ "post",
+ f"{SHOP}/orders",
+ None,
+ {
+ "customer": {"email": "a@b.test"},
+ "cart": {"items": [{"ecommerce_product_id": 7}]},
+ },
+ ),
+ (
+ lambda c: c.ecommerce_orders.update(1, 9, status="complete"),
+ "put",
+ f"{SHOP}/orders/9",
+ None,
+ {"status": "complete"},
+ ),
+ (
+ lambda c: c.ecommerce_orders.delete(1, 9),
+ "delete",
+ f"{SHOP}/orders/9",
+ None,
+ None,
+ ),
+ (lambda c: c.ecommerce_carts.list(1), "get", f"{SHOP}/carts", {}, None),
+ (lambda c: c.ecommerce_carts.get(1, 4), "get", f"{SHOP}/carts/4", None, None),
+ (
+ lambda c: c.ecommerce_carts.update(1, 4, cart_total=10),
+ "put",
+ f"{SHOP}/carts/4",
+ None,
+ {"cart_total": 10},
+ ),
+ (
+ lambda c: c.ecommerce_cart_items.list(1, 4),
+ "get",
+ f"{SHOP}/carts/4/items",
+ {},
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_cart_items.get(1, 4, 8),
+ "get",
+ f"{SHOP}/carts/4/items/8",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_cart_items.create(1, 4, ecommerce_product_id=7),
+ "post",
+ f"{SHOP}/carts/4/items",
+ None,
+ {"ecommerce_product_id": 7},
+ ),
+ (
+ lambda c: c.ecommerce_cart_items.update(1, 4, 8, quantity=2),
+ "put",
+ f"{SHOP}/carts/4/items/8",
+ None,
+ {"quantity": 2},
+ ),
+ (
+ lambda c: c.ecommerce_cart_items.delete(1, 4, 8),
+ "delete",
+ f"{SHOP}/carts/4/items/8",
+ None,
+ None,
+ ),
+ (
+ lambda c: c.ecommerce_imports.products(1, [{"name": "Hat"}]),
+ "post",
+ f"{SHOP}/products/import",
+ None,
+ [{"name": "Hat"}],
+ ),
+ (
+ lambda c: c.ecommerce_imports.categories(1, [{"name": "Hats"}]),
+ "post",
+ f"{SHOP}/categories/import",
+ None,
+ [{"name": "Hats"}],
+ ),
+ (
+ lambda c: c.ecommerce_imports.customers(1, [{"email": "a@b.test"}]),
+ "post",
+ f"{SHOP}/customers/import",
+ None,
+ [{"email": "a@b.test"}],
+ ),
+ (
+ lambda c: c.ecommerce_imports.orders(
+ 1, [{"customer": {"email": "a@b.test"}, "cart": {"items": []}}]
+ ),
+ "post",
+ f"{SHOP}/orders/import",
+ None,
+ {"orders": [{"customer": {"email": "a@b.test"}, "cart": {"items": []}}]},
+ ),
+ (
+ lambda c: c.campaigns.ecommerce_stats(2, 1),
+ "get",
+ f"{API}/campaigns/2/reports/ecommerce",
+ {"shop_id": 1},
+ None,
+ ),
+ (
+ lambda c: c.campaigns.ecommerce_orders(2, 1, page=2),
+ "get",
+ f"{API}/campaigns/2/reports/ecommerce/orders",
+ {"shop_id": 1, "page": 2},
+ None,
+ ),
+ (
+ lambda c: c.subscribers.ecommerce_stats(6, 1),
+ "get",
+ f"{API}/subscribers/6/reports/ecommerce",
+ {"shop_id": 1},
+ None,
+ ),
+ (
+ lambda c: c.subscribers.ecommerce_orders(6, 1),
+ "get",
+ f"{API}/subscribers/6/reports/ecommerce/orders",
+ {"shop_id": 1},
+ None,
+ ),
+ (
+ lambda c: c.automations.ecommerce_stats(8),
+ "get",
+ f"{API}/automations/8/monthly-totals/ecommerce",
+ {},
+ None,
+ ),
+ (
+ lambda c: c.automations.email_ecommerce_stats(8, 9, shop_id=1),
+ "get",
+ f"{API}/automations/8/emails/9/ecommerce-overview-stats",
+ {"shop_id": 1},
+ None,
+ ),
+ (
+ lambda c: c.automations.ecommerce_orders(8, "all_time"),
+ "get",
+ f"{API}/automations/8/order-activity",
+ {"time_frame": "all_time"},
+ None,
+ ),
+]
+
+
+class TestEcommerce:
+ @classmethod
+ def setup_class(self):
+ self.client = MailerLite.Client({"api_key": "test-key"})
+
+ @pytest.mark.parametrize("call, method, url, params, body", REQUESTS)
+ def test_request(self, mocker, call, method, url, params, body):
+ request = mocker.patch("requests.request")
+ request.return_value.json.return_value = {}
+
+ call(self.client)
+
+ kwargs = request.call_args.kwargs
+ assert kwargs["method"] == method
+ assert kwargs["url"] == url
+ assert kwargs["params"] == params
+ if body is not None:
+ assert json.loads(kwargs["data"]) == body
+
+ def test_rejects_a_non_integer_id(self):
+ with pytest.raises(TypeError):
+ self.client.ecommerce_products.get(1, "7")
+
+ def test_rejects_a_non_integer_email_id(self):
+ with pytest.raises(TypeError):
+ self.client.automations.email_ecommerce_stats(8, "9")
+
+ def test_rejects_an_unknown_list_argument(self):
+ with pytest.raises(TypeError):
+ self.client.ecommerce_products.list(1, unknown=True)
diff --git a/tests/groups_test.py b/tests/groups_test.py
index 93d03fb..d695164 100644
--- a/tests/groups_test.py
+++ b/tests/groups_test.py
@@ -122,7 +122,9 @@ def test_given_incorrect_subscribers_when_calling_import_subscribers_to_group_th
with pytest.raises(TypeError):
self.client.groups.import_subscribers_to_group(1234, "not-a-list")
- @pytest.mark.skip(reason="VCR cassette was recorded with a 401 response and needs to be re-recorded")
+ @pytest.mark.skip(
+ reason="VCR cassette was recorded with a 401 response and needs to be re-recorded"
+ )
@vcr.use_cassette(
"tests/vcr_cassettes/groups-import-subscribers.yml",
filter_headers=["Authorization"],
@@ -131,7 +133,9 @@ def test_given_correct_parameters_when_calling_import_subscribers_to_group_then_
self,
):
subscribers = [{"email": "test@example.com", "name": "Test User"}]
- response = self.client.groups.import_subscribers_to_group(pytest.entity_id, subscribers)
+ response = self.client.groups.import_subscribers_to_group(
+ pytest.entity_id, subscribers
+ )
assert isinstance(response, dict)