Разработка системы подписки на регулярные поставки для интернет-магазина

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка системы подписки на регулярные поставки для интернет-магазина
Сложная
~1-2 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка системы подписки на регулярные поставки для интернет-магазина

Подписка меняет экономику интернет-магазина: покупатель больше не принимает решение о покупке каждый раз — он принял его один раз. Для магазина это предсказуемый денежный поток и снижение стоимости повторной продажи. Технически это одна из сложнейших задач в e-commerce: рекуррентные платежи, управление расписанием, обработка сбоев оплаты, управление паузами и отменами.

Архитектура системы

Система подписок состоит из трёх независимых слоёв:

1. Subscription core — хранение планов, подписок, периодов 2. Billing engine — рекуррентные платежи, retry-логика, dunning 3. Fulfillment — генерация заказов и управление отгрузкой

Разделение принципиально: сбой биллинга не должен блокировать выполнение уже оплаченных подписок, а изменение плана не должно ломать текущие биллинг-циклы.

Схема данных

CREATE TABLE subscription_plans (
    id              BIGSERIAL PRIMARY KEY,
    name            VARCHAR(255) NOT NULL,
    slug            VARCHAR(100) NOT NULL UNIQUE,
    interval_type   VARCHAR(20) NOT NULL, -- day | week | month | year
    interval_count  INTEGER NOT NULL DEFAULT 1,
    trial_days      INTEGER NOT NULL DEFAULT 0,
    is_active       BOOLEAN NOT NULL DEFAULT true
);

CREATE TABLE subscription_plan_items (
    id          BIGSERIAL PRIMARY KEY,
    plan_id     BIGINT NOT NULL REFERENCES subscription_plans(id),
    variant_id  BIGINT NOT NULL REFERENCES product_variants(id),
    qty         INTEGER NOT NULL DEFAULT 1,
    discount    NUMERIC(5,2) NOT NULL DEFAULT 0 -- скидка подписчика в %
);

CREATE TABLE subscriptions (
    id                  BIGSERIAL PRIMARY KEY,
    user_id             BIGINT NOT NULL REFERENCES users(id),
    plan_id             BIGINT NOT NULL REFERENCES subscription_plans(id),
    status              VARCHAR(50) NOT NULL DEFAULT 'active',
                        -- trial | active | paused | past_due | cancelled | expired
    payment_method_id   VARCHAR(255) NOT NULL, -- токен из платёжной системы
    current_period_start TIMESTAMP NOT NULL,
    current_period_end   TIMESTAMP NOT NULL,
    trial_ends_at        TIMESTAMP,
    paused_at            TIMESTAMP,
    resumes_at           TIMESTAMP,
    cancelled_at         TIMESTAMP,
    cancel_reason        TEXT,
    created_at           TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE subscription_billing_attempts (
    id              BIGSERIAL PRIMARY KEY,
    subscription_id BIGINT NOT NULL REFERENCES subscriptions(id),
    amount          NUMERIC(12,2) NOT NULL,
    status          VARCHAR(50) NOT NULL, -- success | failed | pending
    payment_id      VARCHAR(255),
    failure_code    VARCHAR(100),
    failure_message TEXT,
    attempted_at    TIMESTAMP NOT NULL DEFAULT NOW(),
    next_retry_at   TIMESTAMP
);

Рекуррентные платежи

Ключевой вопрос — какой платёжный шлюз поддерживает токенизацию карты и серверные списания без участия пользователя:

Шлюз Рекуррент Особенности
Stripe PaymentIntents + SetupIntents Лучшее API, SCA-ready
ЮKassa recurring_object Актуально для РФ
CloudPayments token-платежи Подходит для СНГ
Robokassa Рекуррент через Recurring Базовый функционал

Пример с CloudPayments — типичный для Беларуси/России:

class RecurringBillingService
{
    public function chargeSubscription(Subscription $subscription): BillingAttempt
    {
        $amount = $this->calculateAmount($subscription);

        $attempt = BillingAttempt::create([
            'subscription_id' => $subscription->id,
            'amount'          => $amount,
            'status'          => 'pending',
        ]);

        try {
            $result = $this->cloudpayments->chargeToken([
                'Token'       => $subscription->payment_method_id,
                'Amount'      => $amount,
                'Currency'    => 'RUB',
                'AccountId'   => $subscription->user_id,
                'Description' => "Подписка #{$subscription->id}",
            ]);

            $attempt->update(['status' => 'success', 'payment_id' => $result->TransactionId]);
            $this->advancePeriod($subscription);

        } catch (PaymentDeclinedException $e) {
            $attempt->update([
                'status'          => 'failed',
                'failure_code'    => $e->getCode(),
                'failure_message' => $e->getMessage(),
                'next_retry_at'   => $this->calculateRetryTime($attempt),
            ]);
            $this->handleFailedPayment($subscription, $attempt);
        }

        return $attempt;
    }
}

Dunning — обработка просроченных платежей

Dunning — процесс повторных попыток списания при неудаче. Стандартная схема:

День 0: Списание не прошло → статус past_due, попытка 1
День 3: Retry попытка 2 + email "Проблема с оплатой"
День 7: Retry попытка 3 + email с кнопкой обновить карту
День 14: Retry попытка 4 + SMS
День 21: Подписка отменяется автоматически

Каждый шаг — джоб в очереди:

// Планируется при неудачном списании
RetrySubscriptionPayment::dispatch($subscription)
    ->delay(now()->addDays(3));

// Планируется при второй неудаче
SendDunningEmail::dispatch($subscription, 'update_card')
    ->delay(now()->addDays(7));

Важно: при неудаче сервис всё равно отгружает текущий период — подписчик не должен страдать за технический сбой. Подписка переходит в past_due, но заказ создаётся.

Управление паузами

Пауза — конкурентное преимущество перед полной отменой. Подписчик указывает дату возобновления:

class SubscriptionManager
{
    public function pause(Subscription $subscription, Carbon $resumeAt): void
    {
        if ($resumeAt->lte(now())) {
            throw new InvalidResumeDateException();
        }

        $subscription->update([
            'status'      => 'paused',
            'paused_at'   => now(),
            'resumes_at'  => $resumeAt,
        ]);

        // Отменяем следующее списание
        $subscription->pendingBillingJobs()->delete();

        // Планируем возобновление
        ResumeSubscription::dispatch($subscription)->delay($resumeAt);
    }
}

Максимальный срок паузы — конфигурируемый (обычно 3 месяца). Паузы в середине периода: неиспользованное время не «сгорает» — следующий период начинается от resumes_at.

Кастомизация подписки

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

  • Изменить состав (добавить/убрать товары)
  • Изменить объём (×1, ×2 и т.д.)
  • Изменить интервал (раз в 2 недели → раз в месяц)
  • Пропустить один период
  • Поменять адрес доставки
  • Обновить карту

Изменение тарифного плана mid-cycle требует пересчёта суммы и возможного кредитования или доначисления:

// Upgrade в середине цикла: docharge за разницу
$unusedDays = now()->diffInDays($subscription->current_period_end);
$totalDays  = $subscription->current_period_start->diffInDays($subscription->current_period_end);
$prorationFactor = $unusedDays / $totalDays;
$chargeNow = ($newPlan->price - $currentPlan->price) * $prorationFactor;

Генерация заказов

В день отгрузки создаётся реальный заказ:

class SubscriptionOrderFactory
{
    public function createFromSubscription(Subscription $subscription): Order
    {
        return DB::transaction(function () use ($subscription) {
            $order = Order::create([
                'user_id'         => $subscription->user_id,
                'source'          => 'subscription',
                'subscription_id' => $subscription->id,
                'delivery_address_id' => $subscription->delivery_address_id,
            ]);

            foreach ($subscription->plan->items as $planItem) {
                $price = $planItem->variant->price
                    * (1 - $planItem->discount / 100);

                $order->items()->create([
                    'variant_id' => $planItem->variant_id,
                    'qty'        => $planItem->qty,
                    'unit_price' => $price,
                ]);

                $this->stockService->reserve($planItem->variant_id, $planItem->qty);
            }

            return $order;
        });
    }
}

Личный кабинет подписчика

Минимальный UI для управления подпиской:

  • История поставок с возможностью просмотра каждого заказа
  • Ближайшая дата следующей отгрузки
  • Кнопки: «Пропустить следующую», «Пауза», «Отменить»
  • Управление составом и объёмом
  • История платежей с возможностью скачать чек

Сроки реализации

  • Базовые подписки + рекуррентный биллинг (один шлюз): 7–10 дней
  • Dunning + retry-логика + уведомления: 3–4 дня
  • Управление паузами, пропусками, апгрейдом плана: 3–4 дня
  • Личный кабинет подписчика: 3–5 дней
  • Аналитика (MRR, churn, LTV): +2–3 дня

Полная система: 3–5 недель в зависимости от платёжного шлюза и сложности тарифной сетки.