Войти Регистрация
Код-ревью

30. Ночное продление подписки МТС

Средняя

Kotlin · 42 строк

Каждую ночь в 00:10 по Москве джоба списывает абонентскую плату за подписку: цена тарифа минус персональная скидка (discountPercent — целое, 0–50). После четырёх неудачных списаний подряд подписка приостанавливается, до этого её пробуют списать снова на следующую ночь.

Активных подписок 6,2 млн. JVM на серверах запущена в UTC, monthlyPrice — Double. Отметьте строки с ошибками.

В работе: Подписка с кино, музыкой и кешбэком: списание в ночь даты продления и SMS абоненту.

Как проверяем Нажмите на строку с ошибкой и коротко напишите, что не так. Баг засчитан, если отмечена хотя бы одна его строка. Зачёт — найдено не меньше 60 % багов с учётом серьёзности (критичный весит 3, обычный 2, мелкий 1), а лишних отметок не больше, чем багов в коде. Пустые строки, скобки и комментарии лишними не считаем. Ответ один на подход: после него показываем разбор.
Springпланировщикчасовые поясаденьгилоги

Источник: наша задача на данных МТС: подписки и списания со счёта. Условие пересказано своими словами.

  1. @Service
  2. class SubscriptionRenewalJob(
  3. private val subscriptions: SubscriptionRepository,
  4. private val billing: BillingClient,
  5. private val sms: SmsGateway,
  6. ) {
  7. private val log = LoggerFactory.getLogger(javaClass)
  8. @Scheduled(cron = "0 10 0 * * *", zone = "Europe/Moscow")
  9. fun renewAll() {
  10. val today = LocalDate.now()
  11. val due = subscriptions.findAll()
  12. .filter { it.nextChargeDate == today && it.status == Status.ACTIVE }
  13. for (sub in due) {
  14. renew(sub)
  15. }
  16. }
  17. fun renew(sub: Subscription) {
  18. if (sub.trialEndsAt != null && sub.trialEndsAt!!.isAfter(LocalDate.now())) {
  19. sub.nextChargeDate = sub.trialEndsAt!!
  20. subscriptions.save(sub)
  21. return
  22. }
  23. val discount = sub.discountPercent / 100
  24. val price = sub.tariff.monthlyPrice * (1 - discount)
  25. try {
  26. val result = billing.charge(sub.msisdn, price)
  27. if (result.status == "OK") {
  28. sub.nextChargeDate = sub.nextChargeDate.plusDays(30)
  29. sub.failedAttempts = 0
  30. } else {
  31. sub.failedAttempts++
  32. if (sub.failedAttempts > 3) sub.status = Status.SUSPENDED
  33. }
  34. } catch (e: Exception) {
  35. log.warn("charge failed for ${sub.msisdn}, auth=${billing.token}", e)
  36. }
  37. subscriptions.save(sub)
  38. sms.send(sub.msisdn, "Подписка ${sub.tariff.name} продлена, списано $price ₽")
  39. }
  40. }