В бюро появилось несколько продуктов, оплачиваемых пользователями банковской картой. Продукты разрабатывались в разное время и продавались по разным схемам. В итоге интерфейсы оплаты Школы стажёров, шрифтов и электронных книг выглядели, работали и поддерживались каждый по‑своему.

Поэтому когда возникла частная техническая задача переписать с нуля билинг электронных книг, мы решили начать с дизайна «полиморфичного» программного интерфейса билинга, кассы и почты, к которым потом смогут подключиться и остальные продукты бюро:

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

Бюробилинг — веб‑сервис, отвечающий за продажу продуктов и услуг: подписки, тарифы и автопродление. Определяет, когда и сколько списать по инициативе покупателя, при автопродлении и при других условиях. Подтверждает покупателю операции по почте и в интерфейсе. Напоминает о просроченных платежах. У каждого продукта может быть свой билинг. Метафора — продавец продукта.

Бюрокасса — веб‑сервис, отвечающий за списания с пользователей Бюросферы по заданию билинга, подтверждает списание продавцу. Метафора — бухгалтер и кассир.

Спецификация задачи «Бюробилинг и бюрокасса в издательстве бюро»

Одновременно с написанием спецификации мы исследовали старый билинг. В первую очередь нужно было понять и задокументировать, из чего состоит система, которую мы собираемся заменить: автоплатежи, отмены, письма, списания, покупки себе и в подарок. Затем необходимо было определить «линии отреза»: места в коде, где нам нужно заменить старую систему на новую.

Ещё до начала разработки самих сервисов мы решили, что будем запускать их частями и как можно раньше: запустить и отладить 100 строк кода гораздо проще, чем 3000 строк, накопленных за месяц.

Чтобы минимизировать проблемы с подписками, списаниями и стабильностью, делали микропуски каждый день, постепенно увеличивая количество подписчиков, работающих с новым кодом. Переход на новые сервисы занял две недели:

7 ноября, понедельник

Настраиваем и выкатываем Бюрокассу в продакшен. Пока с ней никто не работает.

Готовим к пуску подписку в подарок на стейджинге.

8 ноября, вторник

Перепроверяем подписку в подарок на стейджинге: продление, отмену, напоминания и покупку в подарок.

Проверяем боевую Бюрокассу: как реагирует на таймауты, как на нагрузку?

9 ноября, среда

Мигрируем подписки в подарок, их коды и платежи в новый билинг.

Подключаем новый билинг к боевой Бюрокассе и боевому сайту: подписки в подарок начинают работать через новый бэкенд, остальные — через старый.

Вручную запускаем очередь автоплатежей по подарочным подпискам на минимальных выборках по 10 человек.

10 ноября, четверг

Чиним обнаруживающиеся проблемы с подписками в подарок, если есть.

Готовим к пуску обычные подписки на стейджинге.

11 ноября, пятница

Перепроверяем обычную подписку на стейджинге: продление, отмену и покупку.

14 ноября, понедельник

Мигрируем обычные подписки и платежи по ним в новый билинг.

Подключаем обычные подписки к новому билингу.

Запускаем очередь автоплатежей в ручном режиме: при каждом запуске обрабатываем 10 подписок, проверяем схему работы.

15 ноября, вторник

Запускаем очередь автоплатежей в ручном режиме: обрабатываем по 20 подписок, контролируем вручную.

Проверяем инструмент аудита, который ищет подозрительные платежи и, если они находятся, останавливает автоплатежи и бьёт тревогу.

16 ноября, среда

Запускаем инструменты аудита.

Запускаем очередь автоплатажей в ручном режиме: обрабатываем по 40 подписок, контролируем вручную.

17 ноября, четверг

Запускаем очередь автоплатежей в автоматическом режиме.

Самым сложным в пуске оказались миграции данных из старого билинга в новый: билинги использовали разные базы данных и разные подходы к именованию колонок в них, данные в старом билинге были доступны только через SSH. Чтобы решить эти проблемы, подключили старую базу данных через SSH‑туннель и стандартный АПИ ActiveRecord:

require "net/ssh/gateway"
require "mysql2"
​
# Модель, которая даёт доступ к данным в старой БД
# через стандартный АПИ ActiveRecord
class LegacySubscription < ApplicationRecord
  self.table_name = "book_subscription"
  ​
  scope :regular, -> { where(type: 1) }
  scope :gifts, -> { where(type: 5) }
  ​
  # Конвертируем информацию о статусе подписки
  # из старого числового формата в новый
  def status_text
    return "active" if status == 1
    return "inactive" if status.zero?
    return "cancelled" if status == 2
  end
end
​
def open_gateway
  gateway = Net::SSH::Gateway.new("olddb.ssh.masterhost.ru", "olddb")
  gateway.open("olddb.mysql.masterhost.ru", 3306, 3307)
  ​
  gateway
end
​
def within_ssh_tunnel_to_legacy_subscriptions
  # Открываем ssh-туннель до старой БД
  gateway = open_gateway
  ​
  connection_params = {
    adapter: "mysql2",
    database: "olddb",
    port: 3307
    password: ENV["DB_IMPORT_PASS"]
  }
  ​
  fork do
    # Соединяем LegacySubscription со старой БД
    # и передаем управление импорту
    LegacySubscription.establish_connection(connection_params)
  ​
    yield
  end
  ​
  Process.wait
  gateway.shutdown!
end

Импорт оформили рейк‑тасками:

namespace :import do
  desc "Import regular subscriptions from artgorbunov.ru"
  task self_subscriptions: :environment do
    within_ssh_tunnel_to_legacy_subscriptions do
      scope = LegacySubscription.regular
      ​
      scope.find_each do |legacy_subscription|
        subscription = Subscription.find_or_initialize_by(
          legacy_subscription_id: legacy_subscription.id
        )
        ​
        subscription.subscription_type = "self"
        subscription.owner_uid = legacy_subscription.ownerId
        subscription.user_uid = subscription.owner_uid
        ​
        subscription.valid_until = legacy_subscription.validUntil
        subscription.issued_at = legacy_subscription.createdDate
        ​
        subscription.status = legacy_subscription.status_text
        subscription.auto_renew = legacy_subscription.autorenew
        ​
        subscription.data = legacy_subscription.data
        ​
        subscription.save
      end
    end
  end
end
Веб‑разработка
Отправить
Поделиться
Запинить

Рекомендуем другие советы