В бюро появилось несколько продуктов, оплачиваемых пользователями банковской картой. Продукты разрабатывались в разное время и продавались по разным схемам. В итоге интерфейсы оплаты Школы стажёров, шрифтов и электронных книг выглядели, работали и поддерживались каждый по‑своему.
Поэтому когда возникла частная техническая задача переписать с нуля билинг электронных книг, мы решили начать с дизайна «полиморфичного» программного интерфейса билинга, кассы и почты, к которым потом смогут подключиться и остальные продукты бюро:
Задача: отделить списания (процессинг, кассу) от билинга (тарифы, подписки, доступные книги), чтобы минимизировать проблемы с автоплатежами. Заложить фундамент для будущих билингов на примере издательства.
Бюробилинг — веб‑сервис, отвечающий за продажу продуктов и услуг: подписки, тарифы и автопродление. Определяет, когда и сколько списать по инициативе покупателя, при автопродлении и при других условиях. Подтверждает покупателю операции по почте и в интерфейсе. Напоминает о просроченных платежах. У каждого продукта может быть свой билинг. Метафора — продавец продукта.
Бюрокасса — веб‑сервис, отвечающий за списания с пользователей Бюросферы по заданию билинга, подтверждает списание продавцу. Метафора — бухгалтер и кассир.
Одновременно с написанием спецификации мы исследовали старый билинг. В первую очередь нужно было понять и задокументировать, из чего состоит система, которую мы собираемся заменить: автоплатежи, отмены, письма, списания, покупки себе и в подарок. Затем необходимо было определить «линии отреза»: места в коде, где нам нужно заменить старую систему на новую.
Ещё до начала разработки самих сервисов мы решили, что будем запускать их частями и как можно раньше: запустить и отладить 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