in membership-common/src/main/scala/com/gu/memsub/subsv2/services/SubscriptionService.scala [164:222]
private def getSubscriptionsFromAccount(accountId: AccountId)(implicit logPrefix: LogPrefix): M[Disjunction[String, List[Subscription]]] =
rest.get[List[Subscription]](s"subscriptions/accounts/${accountId.get}")(multiSubJsonReads, implicitly)
/** fetched with /v1/subscription/{key}?charge-detail=current-segment which zeroes out all the non-active charges
*
* There are multiple scenarios
* - period between acquisition date and fulfilment date => None which indicates cancel now
* - usually contractEffectiveDate to customerAcceptanceDate, except in the case of Guardian Weekly+6for6 where customerAcceptanceDate
* indicates start of GW proper invoiced period instead of start of 6for6 invoiced period despite GW+6for6 being just a regular Subscription
* with multiple products.
* - free trial, or user choose start date of first issue in the future (lead time)
* - Subscription within invoiced period proper => Some(endOfLastInvoicePeriod)
* - free product => None which indicates cancel now
* - edge case of being on the first day of invoice period however bill run has not yet happened => ERROR
* - Today is after end of last invoice date and bill run has already completed => ERROR
*
* @return
* Right(None) indicates cancel now, Right(Some("yyyy-mm-dd")) indicates cancel at end of last invoiced period Left indicates error and MMA
* should not proceed with automatic cancelation
*/
def decideCancellationEffectiveDate(
subscriptionNumber: memsub.Subscription.SubscriptionNumber,
wallClockTimeNow: LocalTime = LocalTime.now(),
today: LocalDate = LocalDate.now(),
)(implicit logPrefix: LogPrefix): EitherT[String, M, Option[LocalDate]] = {
EitherT(
OptionT(get(subscriptionNumber, isActiveToday = true)).fold(
zuoraSubscriptionWithCurrentSegment => {
val paidPlans =
zuoraSubscriptionWithCurrentSegment.ratePlans
val billRunHasAlreadyHappened = wallClockTimeNow.isAfter(BillRunCompletedByTime)
paidPlans match {
case paidPlan1 :: paidPlan2 :: _ => \/.l[Option[LocalDate]]("Failed to determine specific single active paid rate plan charge")
case paidPlan :: Nil => // single rate plan charge identified
paidPlan.chargedThroughDate match {
case Some(endOfLastInvoicePeriod) =>
val endOfLastInvoiceDateIsBeforeOrOnToday = endOfLastInvoicePeriod.isBefore(today) || endOfLastInvoicePeriod.isEqual(today)
if (endOfLastInvoiceDateIsBeforeOrOnToday && billRunHasAlreadyHappened)
\/.left(
"chargedThroughDate exists but seems out-of-date because bill run should have moved chargedThroughDate to next invoice period. Investigate ASAP!",
)
else
\/.r[String](Some(endOfLastInvoicePeriod))
case None =>
if (paidPlan.effectiveStartDate.equals(today) && !billRunHasAlreadyHappened) // effectiveStartDate exists but not chargedThroughDate
\/.l[Option[LocalDate]](s"Invoiced period has started today, however Bill Run has not yet completed (it usually runs around 6am)")
else
\/.l[Option[LocalDate]](s"Unknown reason for missing chargedThroughDate. Investigate ASAP!")
}
case Nil => \/.r[String](Option.empty[LocalDate]) // free product so cancel now
}
},
none = \/.right(Option.empty[LocalDate]),
), // we are within period between acquisition date and fulfilment date so cancel now (lead time / free trial)
)
}