Skip to content

Commit

Permalink
Dashamail directcrm (#2141)
Browse files Browse the repository at this point in the history
* Split dashamail app into two parts -- lists and directcrm
* Notifying dashamail directcrm about new and orders
* Subscribing users to the dedicted per-productgroup mailing lists
  • Loading branch information
f213 authored Dec 13, 2023
1 parent 5e61ffa commit 219fa58
Show file tree
Hide file tree
Showing 45 changed files with 840 additions and 605 deletions.
7 changes: 7 additions & 0 deletions src/apps/dashamail/directcrm/events/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from apps.dashamail.directcrm.events.order_created import OrderCreated
from apps.dashamail.directcrm.events.order_paid import OrderPaid

__all__ = [
"OrderCreated",
"OrderPaid",
]
46 changes: 46 additions & 0 deletions src/apps/dashamail/directcrm/events/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from abc import ABCMeta
from abc import abstractmethod
from abc import abstractproperty
from decimal import Decimal

import httpx

from django.conf import settings

from apps.dashamail import exceptions


class Event(metaclass=ABCMeta):
"""Dashamail DirectCRM backend event
https://lk.dashamail.ru/?page=cdp&action=developer
"""

@abstractproperty
def name(self) -> str:
"""Event name"""

@abstractmethod
def to_json(self) -> dict[str, str | dict]:
"""Actual event payload, 'data' field in the dashamail request"""

@staticmethod
def format_price(price: Decimal) -> str:
return str(price).replace(".", ",")

def send(self) -> None:
response = httpx.post(
url=f"https://directcrm.dashamail.com/v3/operations/sync?endpointId={settings.DASHAMAIL_DIRECTCRM_ENDPOINT}&operation={self.name}",
json=self.to_json(),
headers={
"Authorization": f"Dashamail secretKey={settings.DASHAMAIL_DIRECTCRM_SECRET_KEY}",
},
)

if response.status_code != 200:
raise exceptions.DashamailDirectCRMHTTPException(f"Wrong HTTP response from dashamail directcrm: {response.status_code}")

response_json = response.json()

if response_json is None or response_json["status"] != "Success":
raise exceptions.DashamailDirectCRMWrongResponse(f"Wrong response from dashamail directcrm: {response_json}")
28 changes: 28 additions & 0 deletions src/apps/dashamail/directcrm/events/order_created.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from apps.dashamail.directcrm.events.base import Event
from apps.orders.models import Order


class OrderCreated(Event):
name = "OrderCreate"

def __init__(self, order: Order):
self.order = order

def to_json(self) -> dict:
return {
"customer": {
"email": self.order.user.email,
},
"order": {
"orderId": self.order.slug,
"totalPrice": self.format_price(self.order.price),
"status": "created",
"lines": [
{
"productId": self.order.course.slug,
"quantity": 1,
"price": self.format_price(self.order.price),
}
],
},
}
28 changes: 28 additions & 0 deletions src/apps/dashamail/directcrm/events/order_paid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from apps.dashamail.directcrm.events.base import Event as DirectCRMEvent
from apps.orders.models import Order


class OrderPaid(DirectCRMEvent):
name = "OrderPaid"

def __init__(self, order: Order):
self.order = order

def to_json(self) -> dict:
return {
"customer": {
"email": self.order.user.email,
},
"order": {
"orderId": self.order.slug,
"totalPrice": self.format_price(self.order.price),
"status": "finished",
"lines": [
{
"productId": self.order.course.slug,
"quantity": 1,
"price": self.format_price(self.order.price),
}
],
},
}
18 changes: 17 additions & 1 deletion src/apps/dashamail/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ class DashamailHTTPException(DashamailException):


class DashamailWrongResponse(DashamailHTTPException):
pass
"""Wrong response from the lists API"""


class DashamailWrongFrontendAPIResponse(DashamailHTTPException):
"""Wrong response from the frontend API"""


class DashamailSubscriptionFailed(DashamailException):
Expand All @@ -16,3 +20,15 @@ class DashamailSubscriptionFailed(DashamailException):

class DashamailUpdateFailed(DashamailException):
pass


class DashamailDirectCRMException(BaseException):
"""Base dashamail directcrm exception"""


class DashamailDirectCRMHTTPException(DashamailDirectCRMException):
"""Wrong HTTP response from dashamail directcrm"""


class DashamailDirectCRMWrongResponse(DashamailDirectCRMException):
"""Wrong resposne from dashamail directcrm"""
5 changes: 0 additions & 5 deletions src/apps/dashamail/lists/__init__.py

This file was deleted.

99 changes: 0 additions & 99 deletions src/apps/dashamail/lists/client.py

This file was deleted.

7 changes: 7 additions & 0 deletions src/apps/dashamail/lists/dto/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from apps.dashamail.lists.dto.list import DashamailList
from apps.dashamail.lists.dto.subscriber import DashamailSubscriber

__all__ = [
"DashamailList",
"DashamailSubscriber",
]
7 changes: 7 additions & 0 deletions src/apps/dashamail/lists/dto/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from apps.dashamail.lists.http import DashamailListsHTTP


class DashamailListsDTO:
@property
def api(self) -> DashamailListsHTTP:
return DashamailListsHTTP()
17 changes: 17 additions & 0 deletions src/apps/dashamail/lists/dto/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from dataclasses import dataclass

from apps.dashamail.lists.dto.base import DashamailListsDTO


@dataclass
class DashamailList(DashamailListsDTO):
list_id: int | None = None
name: str | None = None

def create(self) -> int:
if self.name is None:
raise RuntimeError("List name must be given")

response = self.api.call("lists.add", {"name": self.name})

return response["response"]["data"]["list_id"]
83 changes: 83 additions & 0 deletions src/apps/dashamail/lists/dto/subscriber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING

from django.conf import settings

from apps.dashamail import exceptions
from apps.dashamail.lists.dto.base import DashamailListsDTO
from apps.dashamail.lists.dto.list import DashamailList

if TYPE_CHECKING:
from apps.users.models import User


@dataclass
class DashamailSubscriber(DashamailListsDTO):
user: "User"

def subscribe(self, to: DashamailList | None = None) -> None:
mail_list = to if to is not None else self.get_default_list()

if self.get_member_id(mail_list) is None:
self._subscribe(to=mail_list)
else:
self._update(mail_list)

def _subscribe(self, to: DashamailList) -> None:
response = self.api.call(
"lists.add_member",
{
"list_id": str(to.list_id),
"email": self.format_email(self.user.email),
"merge_1": self.user.first_name,
"merge_2": self.user.last_name,
"merge_3": ";".join(self.user.tags),
},
)

if response["response"]["msg"]["err_code"] != 0:
raise exceptions.DashamailSubscriptionFailed(f"{response}")

def _update(self, mail_list: DashamailList) -> None:
"""Replace old user's fields with new"""

response = self.api.call(
"lists.update_member",
{
"list_id": str(mail_list.list_id),
"merge_1": self.user.first_name,
"merge_2": self.user.last_name,
"member_id": str(self.get_member_id(mail_list)),
"merge_3": ";".join(self.user.tags),
},
)

if response["response"]["msg"]["err_code"] != 0:
raise exceptions.DashamailUpdateFailed(f"{response}")

def get_member_id(self, mail_list: DashamailList) -> int | None:
"""Return tuple which consists of member_id and is_active"""
response = self.api.call(
"lists.get_members",
{
"list_id": str(mail_list.list_id),
"email": self.format_email(self.user.email),
},
)

if response["response"]["msg"]["err_code"] != 0:
return None

return int(response["response"]["data"][0]["id"])

@staticmethod
def get_default_list() -> DashamailList:
return DashamailList(list_id=settings.DASHAMAIL_LIST_ID)

@staticmethod
def format_email(email: str) -> str:
if email.endswith("@ya.ru"):
# Dashamail internally converts ya.ru to yandex.ru, with ya.ru we're going to get nothing
return email.replace("@ya.ru", "@yandex.ru")

return email
Loading

0 comments on commit 219fa58

Please sign in to comment.