Как стать автором
Обновить

Экосистема поддержки. Автоматизация регистрации пользователей средствами Golang

Время на прочтение6 мин
Количество просмотров5.8K

Чем мы занимаемся?


Наша команда разрабатывает платформу программной отправки уведомлений посредством REST API на мобильные устройства. В настоящий момент это push уведомления для iOS устройств, а также SMS благодаря интеграции с Twilio). С целью организации процесса поддержки, а также сообщества пользователей, был выбран ряд информационных систем и сервисов (список см. ниже), которые, с нашей точки зрения, позволяли решить поставленную задачу в кратчайшие сроки и с минимальными усилиями.


Задачи


Автоматизировать выполнение следующих действий:


  • Pегистрация пользователей в системе учета обращений
  • Регистрации пользователей на портале сообщества
  • Управление услугами для пользователей

Используемые системы (и их назначение)


Все нижеперечисленные информационные системы и сервисы имеют REST API интерфейс, позволяющий решить поставленные задачи.


  • Atlassian Jira Service Desk (система учета обращений позьзователей)
  • Atlassian Confluence (информационный портал)
  • Slack (сообщество пользователей)
  • Paddle (распространение/ лицензирование программного обеспечения)

Описание процесса


В нашем случае нижеперечисленные события являются исчерпывающими:


  1. При загрузке программного продукта необходимо создать пользователя в системе учета обращений пользователей, а также отправить приглашение на портал сообщества
  2. При активации коммерческой поддержки необходимо добавить пользователя в соответствующую группу системы обращеня пользователей
  3. При истечении срока коммерческой подписки необходимо удалить пользователя из соответствующей группы

Реализация


В первую очередь, источником всех вышеперечисленных событий процесса является платформа Paddle, а именно их подсистема Events/ Alerts. При наступлении определенного события, Paddle инициирует POST запрос на заранее определенный URL (нашего сервера).
События, которые мы будем обрабатывать:


  • Payment Success (Non-Subscription)
  • Subscription Created
  • Subscription Cancelled

Запрос со стороны Paddle содержит следующую информацию (содержимое запроса со стороны Paddle может отличаться в зависимости от событий описанных выше, однако интересующие нас элементы остаются неизменными в любом случае):


{
  "method": "POST",
  "data": {
    "alert_id": "XXXXXX",
    "alert_name": "subscription_created",
    "cancel_url": "https://checkout.paddle.com/subscription/cancel?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "currency": "USD",
    "email": "name@example.com",
    "event_time": "2016-08-05 13:26:06",
    "next_bill_date": "2017-08-05",
    "passthrough": "",
    "status": "active",
    "subscription_id": "XXXXXX",
    "subscription_plan_id": "XXXXXX",
    "update_url": "https://checkout.paddle.com/subscription/update?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "p_signature": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  }
}

и ожидает в ответ HTTP 200 OK:


{
  "http_code": 200,
  "redirect_url": "",
  "content_type": "text/plain; charset=utf-8",
  "total_time": 1.018837
}

В случае, если ответ отличается от HTTP 200 OK, Paddle будет направлять повторый запрос в сторону нашего сервера на протяжение 72-х часов с интервалом раз в час.


В вышеупомянутом запросе со стороны Paddle нас интересуют параметры alert_name и email которые мы будем использовать для регистрации пользователей в наших информационных системах.


Разбираем запросы со стороны Paddle


type Event struct {
    AlertName          string  `form:"alert_name" binding:"required"`
    Email              string  `form:"email" binding:"required"`
    Status             string  `form:"status"`
    SubscriptionId     int     `form:"subscription_id"`
    OrderId            string  `form:"order_id"`
    SubscriptionPlanId int     `form:"subscription_plan_id"`
    Country            string  `form:"country"`
    Fee                float32 `form:"fee"`
    Currency           string  `form:"currency"`
    Psignature         string  `form:"p_signature"`
    Passthrough        string  `form:"passthrough"`
    PaymentMethod      string  `form:"payment_method"`
    PaymentTax         float32 `form:"payment_tax"`
    SaleGross          float32 `form:"sale_gross"`
    Earnings           float32 `form:"earnings"`
    EventTime          string  `form:"event_time"`
    NextBillDate       string  `form:"next_bill_date"`
}

func (e *Event) ToFields() (fields log.Fields) {
    fields = make(log.Fields)
    fields["alert_name"] = e.AlertName
    fields["email"] = e.Email
    fields["status"] = e.Status
    fields["subscription_id"] = e.SubscriptionId
    fields["order_id"] = e.OrderId
    fields["subscription_plan_id"] = e.SubscriptionPlanId
    fields["country"] = e.Country
    fields["fee"] = e.Fee
    fields["currency"] = e.Currency
    fields["p_signature"] = e.Psignature
    fields["passthrough"] = e.Passthrough
    fields["payment_method"] = e.PaymentMethod
    fields["payment_tax"] = e.PaymentTax
    fields["sale_gross"] = e.SaleGross
    fields["earnings"] = e.Earnings
    fields["event_time"] = e.EventTime
    fields["next_bill_date"] = e.NextBillDate
    return
}

Выполняем маршрутизацию запросов в зависимости от события


func eventPOST(c *gin.Context) {
    status := []bool{}
    c.Bind(&event)
    log.WithFields(event.ToFields()).Info("Processing event")
    switch event.AlertName {
    case "subscription_created":
        status = append(status, InviteToJira(event.Email))
        status = append(status, AddToJiraGroup(event.Email))
        status = append(status, InviteToSlack(event.Email))
    case "payment_succeeded":
        status = append(status, InviteToJira(event.Email))
        status = append(status, InviteToSlack(event.Email))
    case "subscription_cancelled":
        status = append(status, RemoveFromJiraGroup(event.Email))
    default:
        status = append(status, false)
        log.WithFields(log.Fields{
            "event": event.AlertName,
        }).Error("Unknown event")
    }
    for _, s := range status {
        if s == false {
            c.String(http.StatusInternalServerError, "not Ok\n")
            return
        }
    }
    c.String(http.StatusOK, "Ok\n")
    return
}

Далее, в зависимости от полученного события, мы будем их соответствующим образом обрабатывать.


Приглашение пользователя в Slack (портал сообщества)


func InviteToSlack(username string) bool {
    api := slack.New(Conf.Slack.Token)
    err := api.InviteToTeam(Conf.Slack.Team, "", "", username)
    if err != nil {
        if strings.Contains(err.Error(), "already_in_team") {
            log.WithFields(log.Fields{
                "user":       username,
                "slack_team": Conf.Slack.Team,
            }).Warn("User already in Slack team")
            return true
        } else if strings.Contains(err.Error(), "already_invited") {
            log.WithFields(log.Fields{
                "user":       username,
                "slack_team": Conf.Slack.Team,
            }).Warn("User already invited to Slack")
            return true
        }
        log.Error(err)
        return false
    }
    log.WithFields(log.Fields{
        "user": username,
    }).Warn("User successfuly invited to Slack")
    return true
}

Приглашение/ регистрация пользователя в Jira (система учета обращений позьзователей)


func InviteToJira(username string) bool {
    jira := jira.New(Conf.Jira.Host, Conf.Jira.User, Conf.Jira.Password, false)
    _, err := jira.GetUser(username)
    if err != nil {
        if err.Error() == "Not Found" {
            ok, err := jira.InviteSdUser(username, Conf.Jira.Invite_uri)
            if ok {
                log.WithFields(log.Fields{
                    "user": username,
                }).Info("Invited user to JIRA")
                return true
            } else {
                log.WithFields(log.Fields{
                    "user":  username,
                    "error": err,
                }).Error("Invite to JIRA Failed")
                return false
            }
        } else {
            log.Error(err)
            return false
        }
    }
    log.WithFields(log.Fields{
        "user": username,
    }).Warn("User already Invited to JIRA")
    return true
}

Добавления пользователя в группу Jira (коммерческая поддержка)


func AddToJiraGroup(username string) bool {
    jira := jira.New(Conf.Jira.Host, Conf.Jira.User, Conf.Jira.Password, false)
    ok, err := jira.AddToGroup(username, Conf.Jira.Invite_group)
    if ok {
        log.WithFields(log.Fields{
            "user":  username,
            "group": Conf.Jira.Invite_group,
        }).Info("Added user to Jira Group")
        return true

    } else {
        log.WithFields(log.Fields{
            "user":  username,
            "group": Conf.Jira.Invite_group,
            "error": err,
        }).Error("Failed to add user to Jira Group")
        return false
    }
}

Удаление пользователя из группы Jira (коммерческая поддержка)


func RemoveFromJiraGroup(username string) bool {
    jira := jira.New(Conf.Jira.Host, Conf.Jira.User, Conf.Jira.Password, false)
    ok, err := jira.RemoveFromGroup(username, Conf.Jira.Invite_group)
    if ok {
        log.WithFields(log.Fields{
            "user":  username,
            "group": Conf.Jira.Invite_group,
        }).Info("Removed user from Jira Group")
        return true
    } else {
        log.WithFields(log.Fields{
            "user":  username,
            "group": Conf.Jira.Invite_group,
            "error": err,
        }).Error("Failed to remove User from Jira Group")
        return false
    }
}

Используемые модули (основные)



Заключение


Исходный код описанного выше решения доступен на GitHub: paddle-endpoint.


Благодарю за внимание!

Теги:
Хабы:
Всего голосов 20: ↑17 и ↓3+14
Комментарии10

Публикации

Истории

Работа

Go разработчик
137 вакансий

Ближайшие события