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)