def apply[F[_]: Sync, S]()

in lib/imovo/imovo-sttp-client/src/main/scala/com/gu/imovo/ImovoClient.scala [100:299]


  def apply[F[_]: Sync, S](
      backend: SttpBackend[F, S],
      config: ImovoConfig,
  ): EitherT[F, ImovoClientException, ImovoClient[F]] = {

    def sendAuthenticatedRequest[A: Decoder, B: Encoder](
        apiKey: String,
        method: Method,
        uri: Uri,
        body: Option[B],
    ): EitherT[F, ImovoClientException, A] = {

      val requestWithoutBody = basicRequest
        .method(method, uri)
        .headers(
          Map(
            "X-API-KEY" -> apiKey,
          ),
        )

      val request = body.fold(requestWithoutBody)(b => requestWithoutBody.body(b))

      for {
        response <- request.send(backend).attemptT.leftMap(e => ImovoClientException(e.toString))
        responseBody <- EitherT.fromEither[F](decodeResponse[A](request, response))
      } yield responseBody
    }

    def decodeResponse[A: Decoder](
        request: Request[Either[String, String], Any],
        response: Response[Either[String, String]],
    ): Either[ImovoClientException, A] = {
      response.body.left
        .map(errorBody =>
          ImovoClientException(
            message =
              s"Request ${request.method.method} ${request.uri.toString()} failed returning a status ${response.code} with body: $errorBody",
            responseBody = Some(errorBody),
          ),
        )
        .flatMap { successBody =>
          for {
            parsedResponse <- parse(successBody)
              .leftMap(e =>
                ImovoClientException(
                  message =
                    s"Request ${request.method.method} ${request.uri.toString()} failed to parse response ($successBody): $e",
                  responseBody = Some(successBody),
                ),
              )

            successFlag <- parsedResponse.hcursor
              .downField("successfulRequest")
              .as[Boolean]
              .leftMap(e =>
                ImovoClientException(
                  message = s"Request ${request.method.method} ${request.uri
                      .toString()} had a response which did not contain the successfulRequest flag ($successBody): $e",
                  responseBody = Some(successBody),
                ),
              )

            response <- {
              if (successFlag) {
                parsedResponse
                  .as[A]
                  .leftMap(e =>
                    ImovoClientException(
                      message = s"Request ${request.method.method} ${request.uri
                          .toString()} failed to decode response ($successBody): $e",
                      responseBody = Some(successBody),
                    ),
                  )
              } else {
                ImovoClientException(
                  message =
                    s"Request ${request.method.method} ${request.uri.toString()} failed with response ($successBody)",
                  responseBody = Some(successBody),
                ).asLeft[A]
              }
            }
          } yield response
        }
    }

    val imovoDateFormat = DateTimeFormatter.ISO_DATE

    new ImovoClient[F] {

      override def createSubscriptionVoucher(
          subscriptionId: SfSubscriptionId,
          schemeName: SchemeName,
          startDate: LocalDate,
      ): EitherT[F, ImovoClientException, ImovoSubscriptionResponse] =
        sendAuthenticatedRequest[ImovoSubscriptionResponse, String](
          config.imovoApiKey,
          Method.GET,
          Uri(new URI(s"${config.imovoBaseUrl}/Subscription/RequestSubscriptionVouchers"))
            .addParam("SubscriptionId", subscriptionId.value)
            .addParam("SchemeName", schemeName.value)
            .addParam("StartDate", imovoDateFormat.format(startDate)),
          None,
        )

      override def getSubscriptionVoucher(
          subscriptionId: String,
      ): EitherT[F, ImovoClientException, ImovoSubscriptionResponse] = {
        sendAuthenticatedRequest[ImovoSubscriptionResponse, String](
          config.imovoApiKey,
          Method.GET,
          Uri(new URI(s"${config.imovoBaseUrl}/Subscription/GetSubscriptionVoucherDetails"))
            .addParam("SubscriptionId", subscriptionId),
          None,
        )
      }

      override def replaceSubscriptionVoucher(
          subscriptionId: SfSubscriptionId,
          subscriptionType: ImovoSubscriptionType,
      ): EitherT[F, ImovoClientException, ImovoSubscriptionResponse] = {
        sendAuthenticatedRequest[ImovoSubscriptionResponse, String](
          config.imovoApiKey,
          Method.GET,
          Uri(new URI(s"${config.imovoBaseUrl}/Subscription/ReplaceVoucherBySubscriptionId"))
            .addParam("SubscriptionId", subscriptionId.value)
            .addParam("SubscriptionType", subscriptionType.value),
          None,
        )
      }

      override def cancelSubscriptionVoucher(
          subscriptionId: SfSubscriptionId,
          optionalLastActiveDay: Option[LocalDate],
      ): EitherT[F, ImovoClientException, ImovoSuccessResponse] = {
        val uri = Uri(new URI(s"${config.imovoBaseUrl}/Subscription/CancelSubscriptionVoucher"))
          .addParam("SubscriptionId", subscriptionId.value)
        val uriWithLastActiveDay = optionalLastActiveDay
          .map(lastActiveDay => uri.addParam("LastActiveDay", imovoDateFormat.format(lastActiveDay)))
          .getOrElse(uri)
        sendAuthenticatedRequest[ImovoSuccessResponse, String](
          config.imovoApiKey,
          Method.GET,
          uriWithLastActiveDay,
          None,
        )
      }

      def suspendSubscriptionVoucher(
          subscriptionId: SfSubscriptionId,
          startDate: LocalDate,
          endDateExclusive: LocalDate,
      ): EitherT[F, ImovoClientException, Unit] =
        sendAuthenticatedRequest[ImovoSuccessResponse, String](
          apiKey = config.imovoApiKey,
          method = GET,
          uri = Uri(new URI(s"${config.imovoBaseUrl}/Subscription/SetHoliday"))
            .addParam("SubscriptionId", subscriptionId.value)
            .addParam("StartDate", startDate.toString)
            .addParam("ReactivationDate", endDateExclusive.toString),
          body = None,
        ).map(_ => ())

      /** Method to return `redemptionHistoryMaxLines` of redemption attempts - this call returns successful
        * redemptions, failed redemptions with a reason and any top up credits applied to the subscription
        *
        * The call to imovo has some additional parameters that can be used to paginate the request
        * /Subscription/SubscriptionRedemptionHistory?EndDate=2019-11-23&StartDate=2008-11-23&SubscriptionId=A-S0039247&MaxLines=20
        *
        * REQUIRED
        *
        * CustomerReference - string
        *
        * OPTIONAL
        *
        * StartDate - string (yyyy-MM-dd)
        *
        * EndDate - string (yyyy-MM-dd)
        *
        * MaxLines - integer
        *
        * @return
        *   Either[F, ImovoClientException, ImovoRedemptionHistoryResponse]
        */
      override def getRedemptionHistory(
          subscriptionId: SfSubscriptionId,
      ): EitherT[F, ImovoClientException, ImovoRedemptionHistoryResponse] = {
        val uri = Uri(new URI(s"${config.imovoBaseUrl}/Subscription/SubscriptionRedemptionHistory"))
          .addParam("SubscriptionId", subscriptionId.value)
          .addParam("MaxLines", redemptionHistoryMaxLines)

        sendAuthenticatedRequest[ImovoRedemptionHistoryResponse, String](
          config.imovoApiKey,
          Method.GET,
          uri,
          None,
        )
      }

    }.asRight[ImovoClientException].toEitherT[F]
  }