in handlers/product-move-api/src/main/scala/com/gu/productmove/endpoint/cancel/SubscriptionCancelEndpointSteps.scala [38:126]
private[productmove] def subscriptionCancel(
subscriptionName: SubscriptionName,
postData: ExpectedInput,
identityId: IdentityId,
): Task[OutputBody] = {
val maybeResult: IO[OutputBody | Throwable, Success] = for {
subscriptionAccount <- assertSubscriptionBelongsToIdentityUser(
getSubscription,
getAccount,
subscriptionName,
Some(identityId),
)
_ <- ZIO.log(s"Cancel Supporter Plus - PostData: $postData")
subscription <- getSubscriptionToCancel.get(subscriptionName)
_ <- ZIO.log(s"Subscription is $subscription")
_ <- subscription.status match {
case "Active" => ZIO.succeed(())
case _ => ZIO.fail(BadRequest(s"Subscription $subscriptionName cannot be cancelled as it is not active"))
}
// check sub info to make sure it's a supporter plus subscription
zuoraIds <- ZIO
.fromEither(ZuoraIds.zuoraIdsForStage(config.Stage(stage.toString)).left.map(InternalServerError.apply))
// We have fetched the subscription with the charge-detail=current-segment query param described here:
// https://developer.zuora.com/api-references/api/operation/GET_SubscriptionsByKey/#!in=query&path=charge-detail&t=request
// this means that only the currently active rate plan will contain charge information (even if it has a
// lastChangeType of 'Remove')
ratePlan <- asSingle(
subscription.ratePlans.filter { ratePlan =>
val isDiscount = ratePlan.productName == "Discounts"
// we found that contrary to the docs, if effectiveStartDate and effectiveEndDate are today,
// zuora still returns it as a current-segment, so we need to ignore it
val hasCharges = ratePlan.ratePlanCharges.exists(_.effectiveEndDate != today)
!isDiscount && hasCharges
},
"ratePlan",
)
charges <- asNonEmptyList(
ratePlan.ratePlanCharges,
s"Subscription can't be cancelled as the charge list is empty",
)
supporterPlusCharge <- getSupporterPlusCharge(charges, zuoraIds.supporterPlusZuoraIds)
today <- Clock.currentDateTime.map(_.toLocalDate)
// check whether the sub is within the first 14 days of purchase - if it is then the subscriber is entitled to a refund
shouldBeRefunded = subIsWithinFirst14Days(today, supporterPlusCharge.effectiveStartDate)
_ <- ZIO.log(s"Should be refunded is $shouldBeRefunded")
cancellationDate <- ZIO
.fromOption(
if (shouldBeRefunded)
Some(supporterPlusCharge.effectiveStartDate)
else
supporterPlusCharge.chargedThroughDate,
)
.orElseFail(
InternalServerError(
s"Subscription charged through date is null for supporter plus subscription ${subscriptionName.value}. " +
s"This is an error because we expect to be able to use the charged through date to work out the effective cancellation date",
),
)
_ <- ZIO.log(s"Cancellation date is $cancellationDate")
_ <- ZIO.log(s"Attempting to cancel sub")
cancellationResponse <- zuoraCancel.cancel(subscriptionName, cancellationDate)
_ <- ZIO.log("Sub cancelled as of: " + cancellationDate)
_ <-
if (shouldBeRefunded)
doRefund(subscriptionName, cancellationResponse)
else
ZIO.succeed(RefundResponse("Success", ""))
_ <- ZIO.log(s"Attempting to update cancellation reason on Zuora subscription")
_ <- zuoraSetCancellationReason
.update(
subscriptionName,
subscription.version + 1,
postData.reason,
) // Version +1 because the cancellation will have incremented the version
_ <- sqs.sendEmail(EmailMessage.cancellationEmail(subscriptionAccount._2, cancellationDate))
} yield Success(s"Subscription ${subscriptionName.value} was successfully cancelled")
maybeResult.catchAll {
case failure: OutputBody => ZIO.succeed(failure)
case other: Throwable => ZIO.fail(other)
}
}