-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP get billing preview when holiday stop api is called #2243
base: main
Are you sure you want to change the base?
Changes from all commits
fb58566
e100ac4
cb2a310
9709537
edae643
5436349
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,6 +121,7 @@ object Handler extends Logging { | |
getAccountFromZuora(config, backend), | ||
idGenerator, | ||
FulfilmentDatesFetcher(fetchString, Stage()), | ||
getBillingPreviewFromZuora(config, backend), | ||
now, | ||
)( | ||
request, | ||
|
@@ -135,6 +136,7 @@ object Handler extends Logging { | |
getAccount: (AccessToken, String) => Either[ApiFailure, ZuoraAccount], | ||
idGenerator: => String, | ||
fulfilmentDatesFetcher: FulfilmentDatesFetcher, | ||
getBillingPreview: GetBillingPreview, | ||
now: () => ZonedDateTime, | ||
): (ApiGatewayRequest, HttpOp[StringHttpRequest, BodyAsString]) => ApiResponse = { | ||
(for { | ||
|
@@ -148,6 +150,7 @@ object Handler extends Logging { | |
getAccount, | ||
idGenerator, | ||
fulfilmentDatesFetcher, | ||
getBillingPreview, | ||
now, | ||
)).fold( | ||
{ errorMessage: String => | ||
|
@@ -179,6 +182,7 @@ object Handler extends Logging { | |
getAccount: (AccessToken, String) => Either[ApiFailure, ZuoraAccount], | ||
idGenerator: => String, | ||
fulfilmentDatesFetcher: FulfilmentDatesFetcher, | ||
getBillingPreview: GetBillingPreview, | ||
now: () => ZonedDateTime, | ||
) = { | ||
path match { | ||
|
@@ -189,12 +193,12 @@ object Handler extends Logging { | |
} | ||
case "hsr" :: Nil => | ||
httpMethod match { | ||
case "POST" => stepsToCreate(getAccessToken, getSubscription, getAccount) _ | ||
case "POST" => stepsToCreate(getAccessToken, getSubscription, getAccount, getBillingPreview, now().toLocalDate) _ | ||
case _ => unsupported _ | ||
} | ||
case "bulk-hsr" :: Nil => | ||
httpMethod match { | ||
case "POST" => stepsToBulkCreate(getAccessToken, getSubscription, getAccount) _ | ||
case "POST" => stepsToBulkCreate(getAccessToken, getSubscription, getAccount, getBillingPreview, now().toLocalDate) _ | ||
case _ => unsupported _ | ||
} | ||
case "hsr" :: _ :: Nil => | ||
|
@@ -261,14 +265,14 @@ object Handler extends Logging { | |
.toApiGatewayOp(s"get account ${subscription.accountNumber}") | ||
subscriptionData <- SubscriptionData(subscription, account) | ||
.toApiGatewayOp(s"building SubscriptionData") | ||
issuesData = subscriptionData.issueDataForPeriod(queryParams.startDate, queryParams.endDate) | ||
issuesData = subscriptionData.subscriptionIssueData.issueDataForPeriod(queryParams.startDate, queryParams.endDate) | ||
potentialHolidayStops = issuesData.map { issueData => | ||
PotentialHolidayStop( | ||
issueData.issueDate, | ||
Credit(issueData.credit, issueData.nextBillingPeriodStartDate), | ||
) | ||
} | ||
nextInvoiceDateAfterToday = subscriptionData | ||
nextInvoiceDateAfterToday = subscriptionData.subscriptionIssueData | ||
.issueDataForPeriod(MutableCalendar.today.minusDays(7), MutableCalendar.today.plusMonths(2)) | ||
.filter(_.nextBillingPeriodStartDate.isAfter(MutableCalendar.today)) | ||
.minBy(_.nextBillingPeriodStartDate)(Ordering.by(_.toEpochDay)) | ||
|
@@ -334,31 +338,41 @@ object Handler extends Logging { | |
getAccessToken: () => Either[ApiFailure, AccessToken], | ||
getSubscription: (AccessToken, SubscriptionName) => Either[ApiFailure, Subscription], | ||
getAccount: (AccessToken, String) => Either[ApiFailure, ZuoraAccount], | ||
getBillingPreview: GetBillingPreview, | ||
today: LocalDate, | ||
)(req: ApiGatewayRequest, sfClient: SfClient): ApiResponse = | ||
stepsToCreate( | ||
getAccessToken, | ||
getSubscription, | ||
getAccount, | ||
req.bodyAsCaseClass[HolidayStopRequestPartial](), | ||
getBillingPreview, | ||
today, | ||
)(req, sfClient) | ||
|
||
def stepsToBulkCreate( | ||
getAccessToken: () => Either[ApiFailure, AccessToken], | ||
getSubscription: (AccessToken, SubscriptionName) => Either[ApiFailure, Subscription], | ||
getAccount: (AccessToken, String) => Either[ApiFailure, ZuoraAccount], | ||
getBillingPreview: GetBillingPreview, | ||
today: LocalDate, | ||
)(req: ApiGatewayRequest, sfClient: SfClient): ApiResponse = | ||
stepsToCreate( | ||
getAccessToken, | ||
getSubscription, | ||
getAccount, | ||
req.bodyAsCaseClass[BulkHolidayStopRequestPartial](), | ||
getBillingPreview, | ||
today, | ||
)(req, sfClient) | ||
|
||
private def stepsToCreate( | ||
getAccessToken: () => Either[ApiFailure, AccessToken], | ||
getSubscription: (AccessToken, SubscriptionName) => Either[ApiFailure, Subscription], | ||
getAccount: (AccessToken, String) => Either[ApiFailure, ZuoraAccount], | ||
requestBodyOp: ApiGatewayOp[HolidayStopRequestPartialTrait], | ||
getBillingPreview: GetBillingPreview, | ||
today: LocalDate, | ||
)(req: ApiGatewayRequest, sfClient: SfClient): ApiResponse = { | ||
|
||
val verifyContactOwnsSubOp = | ||
|
@@ -380,8 +394,12 @@ object Handler extends Logging { | |
.toApiGatewayOp(s"get subscription ${requestBody.subscriptionName}") | ||
account <- getAccount(accessToken, subscription.accountNumber) | ||
.toApiGatewayOp(s"get account ${subscription.accountNumber}") | ||
billingPreview <- getBillingPreview | ||
.getBillingPreview(accessToken, subscription.accountNumber, today.plusMonths(13)) | ||
.toApiGatewayOp(s"get billing preview for account ${subscription.accountNumber}") | ||
_ = logger.info("billingPreview: " + billingPreview) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at this stage it just logs the response rather than passing it in to the subscriptiondata. we could try to paralellise the getAccount and getBillingPreview if we want to improve performance. |
||
issuesData <- SubscriptionData(subscription, account) | ||
.map(_.issueDataForPeriod(requestBody.startDate, requestBody.endDate)) | ||
.map(_.subscriptionIssueData.issueDataForPeriod(requestBody.startDate, requestBody.endDate)) | ||
.toApiGatewayOp(s"calculating publication dates") | ||
createBody = CreateHolidayStopRequestWithDetail.buildBody( | ||
requestBody.startDate, | ||
|
@@ -511,7 +529,7 @@ object Handler extends Logging { | |
account <- getAccount(accessToken, subscription.accountNumber) | ||
.toApiGatewayOp(s"get account ${subscription.accountNumber}") | ||
issuesData <- SubscriptionData(subscription, account) | ||
.map(_.issueDataForPeriod(requestBody.startDate, requestBody.endDate)) | ||
.map(_.subscriptionIssueData.issueDataForPeriod(requestBody.startDate, requestBody.endDate)) | ||
.toApiGatewayOp(s"calculating publication dates") | ||
amendBody <- AmendHolidayStopRequest | ||
.buildBody( | ||
|
@@ -552,6 +570,12 @@ object Handler extends Logging { | |
accountKey: String, | ||
): Either[ApiFailure, ZuoraAccount] = Zuora.accountGetResponse(config.zuoraConfig, accessToken, backend)(accountKey) | ||
|
||
def getBillingPreviewFromZuora( | ||
config: Config, | ||
backend: SttpBackend[Identity, Any], | ||
): GetBillingPreview = | ||
GetBillingPreviewLive.billingPreviewGetResponse(config.zuoraConfig, backend) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could inline this, I'm not sure why the convention is to have one function that calls another |
||
|
||
def getAccessTokenFromZuora( | ||
config: Config, | ||
backend: SttpBackend[Identity, Any], | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.gu.holiday_stops | ||
|
||
import cats.Id | ||
import com.gu.holiday_stops.Handler.{getAccessTokenFromZuora, getBillingPreviewFromZuora} | ||
import com.gu.test.EffectsTest | ||
import org.scalatest.flatspec.AnyFlatSpec | ||
import org.scalatest.matchers.should.Matchers | ||
import sttp.client3.HttpURLConnectionBackend | ||
import sttp.client3.logging.{LogLevel, Logger, LoggingBackend} | ||
|
||
import java.time.LocalDate | ||
|
||
class GetBillingPreviewEffectsTest extends AnyFlatSpec with Matchers { | ||
|
||
lazy val config = { | ||
zio.Runtime.default.unsafeRun { | ||
Configuration.config.provideLayer(ConfigurationLive.impl) | ||
} | ||
} | ||
|
||
lazy val backend = LoggingBackend( | ||
HttpURLConnectionBackend(), | ||
new Logger[Id] { | ||
override def apply(level: LogLevel, message: => String): Id[Unit] = info("LOG: " + message) | ||
|
||
override def apply(level: LogLevel, message: => String, t: Throwable): Id[Unit] = | ||
info("LOG: " + message + t.toString) | ||
}, | ||
logResponseBody = true, | ||
) | ||
|
||
"get billing preview" should "fetch a test subscription's preview" taggedAs EffectsTest ignore { | ||
|
||
val ACCOUNT_TO_USE = "A00211577" | ||
|
||
val op = getBillingPreviewFromZuora(config, backend) | ||
val accessToken = getAccessTokenFromZuora(config, backend).toOption.get | ||
|
||
val preview = op.getBillingPreview(accessToken, ACCOUNT_TO_USE, LocalDate.now.plusMonths(13)) | ||
info("preview is: " + preview) | ||
preview.toOption.get.length shouldBe 26 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this line checking, out of interest? we might want to be aware that this is a real account in the api sandbox, and its state may change (it currently has two active subscriptions, but one or both could be cancelled any time, or more could be acquired). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah it's good to flag this - this is more like a manual test, as I couldn't think of a quick way to make it reliable. It should be left as "ignore" unless it's being used. To answer your question it's just checking that 26 items were returned for the 13 months (rather than an error). so not really a completely useful assertion. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.gu.zuora.subscription | ||
|
||
import com.gu.zuora.{AccessToken, ZuoraConfig} | ||
import io.circe.generic.semiauto._ | ||
import io.circe.{Decoder, Encoder, HCursor} | ||
import sttp.client3._ | ||
import sttp.client3.circe._ | ||
|
||
import java.time.LocalDate | ||
|
||
object GetBillingPreview { | ||
|
||
case class InvoiceItem( | ||
serviceStartDate: LocalDate, | ||
serviceEndDate: LocalDate, | ||
chargeId: String, | ||
) | ||
implicit val decode: Decoder[InvoiceItem] = deriveDecoder | ||
// implicit val decode: Decoder[InvoiceItem] = (c: HCursor) => | ||
// for { | ||
// chargeDate <- c.downField("chargeDate").as[String].map(_.takeWhile(_ != ' ')).map(LocalDate.parse) | ||
// chargeId <- c.downField("chargeId").as[String] | ||
// } yield InvoiceItem(chargeDate, chargeId) | ||
|
||
case class BillingPreview( | ||
invoiceItems: List[InvoiceItem], | ||
) | ||
implicit val decode2: Decoder[BillingPreview] = deriveDecoder | ||
implicit val encode: Encoder[BillingPreviewRequest] = deriveEncoder | ||
case class BillingPreviewRequest( | ||
accountNumber: String, | ||
targetDate: LocalDate, | ||
assumeRenewal: String = "Autorenew", | ||
) | ||
} | ||
|
||
trait GetBillingPreview { | ||
import GetBillingPreview._ | ||
def getBillingPreview( | ||
accessToken: AccessToken, | ||
accountNumber: String, | ||
targetDate: LocalDate, // target date is how far in the future to go to | ||
): Either[ApiFailure, List[InvoiceItem]] | ||
} | ||
|
||
// https://developer.zuora.com/v1-api-reference/api/operation/POST_BillingPreview/ | ||
object GetBillingPreviewLive { | ||
|
||
def billingPreviewGetResponse( | ||
config: ZuoraConfig, | ||
backend: SttpBackend[Identity, Any], | ||
): GetBillingPreview = | ||
(accessToken: AccessToken, accountNumber: String, targetDate: LocalDate) => { | ||
val request = GetBillingPreview.BillingPreviewRequest(accountNumber, targetDate) | ||
basicRequest | ||
.post(uri"${config.baseUrl}/operations/billing-preview") | ||
.body(request) | ||
.header("Authorization", s"Bearer ${accessToken.access_token}") | ||
.response(asJson[GetBillingPreview.BillingPreview]) | ||
.mapResponse(_.left.map(e => ZuoraApiFailure(e.getMessage))) | ||
.send(backend) | ||
.body | ||
.map(_.invoiceItems) | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove Mario's invoicing-api experiment (testInProdPreviewPublications) as part of this? This is currently erroring due to a problem with invoicing-api credentials, but if it were working it would also be requesting a billing-preview.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed in #2504