Skip to content

Commit

Permalink
Merge 6714790 into 5fb57ae
Browse files Browse the repository at this point in the history
  • Loading branch information
pvighi committed Aug 22, 2018
2 parents 5fb57ae + 6714790 commit bcefaea
Show file tree
Hide file tree
Showing 27 changed files with 385 additions and 73 deletions.
8 changes: 8 additions & 0 deletions handlers/new-product-api/cfn.yaml
Expand Up @@ -19,10 +19,12 @@ Mappings:
ApiName: new-product-api-CODE
DomainName: new-product-api-code.membership.guardianapis.com
ApiGatewayTargetDomainName: d-ecyddyj7nk.execute-api.eu-west-1.amazonaws.com
ZuoraCatalogLocation: arn:aws:s3:::gu-zuora-catalog/PROD/Zuora-UAT/catalog.json
PROD:
ApiName: new-product-api-PROD
DomainName: new-product-api-prod.membership.guardianapis.com
ApiGatewayTargetDomainName: d-yyh9pmqphi.execute-api.eu-west-1.amazonaws.com
ZuoraCatalogLocation: arn:aws:s3:::gu-zuora-catalog/PROD/Zuora-PROD/catalog.json

Resources:
NewSubscriptionRole:
Expand Down Expand Up @@ -86,6 +88,12 @@ Resources:
- logs:PutLogEvents
- lambda:InvokeFunction
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/product-catalog-${Stage}:log-stream:*"
- PolicyName: ReadZuoraCatalog
PolicyDocument:
Statement:
- Effect: Allow
Action: s3:GetObject
Resource: !FindInMap [ StageMap, !Ref Stage, ZuoraCatalogLocation ]
AddSubscriptionLambda:
Type: AWS::Lambda::Function
Properties:
Expand Down
@@ -0,0 +1,7 @@
package com.gu.newproduct.api.addsubscription

object Formatters {
implicit class AmountOps(amountMinorUnits: AmountMinorUnits) {
def formatted: String = (amountMinorUnits.value / BigDecimal(100)).bigDecimal.setScale(2).toPlainString
}
}
Expand Up @@ -9,7 +9,6 @@ import com.gu.effects.sqs.AwsSQSSend.QueueName
import com.gu.effects.{GetFromS3, RawEffects}
import com.gu.i18n.Currency
import com.gu.newproduct.api.addsubscription.TypeConvert._
import com.gu.newproduct.api.addsubscription.ZuoraIds.{PlanAndCharge, ProductRatePlanId}
import com.gu.newproduct.api.addsubscription.email.EtSqsSend
import com.gu.newproduct.api.addsubscription.email.contributions.SendConfirmationEmailContributions.ContributionsEmailData
import com.gu.newproduct.api.addsubscription.email.contributions.{ContributionFields, SendConfirmationEmailContributions}
Expand All @@ -27,7 +26,8 @@ import com.gu.newproduct.api.addsubscription.zuora.GetContacts.WireModel.GetCont
import com.gu.newproduct.api.addsubscription.zuora.GetPaymentMethod.{DirectDebit, PaymentMethod, PaymentMethodWire}
import com.gu.newproduct.api.addsubscription.zuora.{GetContacts, _}
import com.gu.newproduct.api.productcatalog.PlanId.MonthlyContribution
import com.gu.newproduct.api.productcatalog.{DateRule, NewProductApi, PlanId}
import com.gu.newproduct.api.productcatalog.ZuoraIds.{PlanAndCharge, ProductRatePlanId}
import com.gu.newproduct.api.productcatalog.{DateRule, NewProductApi, PlanId, ZuoraIds}
import com.gu.util.Logging
import com.gu.util.apigateway.ApiGatewayHandler.{LambdaIO, Operation}
import com.gu.util.apigateway.ApiGatewayResponse.internalServerError
Expand Down
@@ -1,6 +1,7 @@
package com.gu.newproduct.api.addsubscription

import com.gu.newproduct.api.addsubscription.validation.{Failed, Passed, ValidationResult}
import com.gu.util.Logging
import com.gu.util.apigateway.ApiGatewayResponse
import com.gu.util.apigateway.ResponseModels.ApiResponse
import com.gu.util.reader.AsyncTypes._
Expand All @@ -9,6 +10,7 @@ import com.gu.util.reader.Types._
import com.gu.util.resthttp.Types.{ClientFailableOp, ClientSuccess, GenericError, NotFound}

import scala.concurrent.Future
import scala.util.{Failure, Success, Try}

object TypeConvert {

Expand Down Expand Up @@ -48,4 +50,12 @@ object TypeConvert {
}
}

implicit class TryToClientFailableOp[A](tryValue: Try[A]) extends Logging {
def toClientFailable(action: String) = tryValue match {
case Success(response) => ClientSuccess(response)
case Failure(exception) => GenericError(s"exception thrown while trying to $action : ${exception.toString}")

}
}

}
Expand Up @@ -2,7 +2,6 @@ package com.gu.newproduct.api.addsubscription.email.contributions

import java.time.LocalDate
import java.time.format.DateTimeFormatter

import com.gu.i18n.Currency
import com.gu.newproduct.api.addsubscription.email.{DataExtensionName, ETPayload}
import com.gu.newproduct.api.addsubscription.zuora.GetContacts.BilltoContact
Expand All @@ -12,7 +11,7 @@ import com.gu.util.Logging
import com.gu.util.apigateway.ApiGatewayResponse
import com.gu.util.reader.AsyncTypes._
import com.gu.util.reader.Types.ApiGatewayOp.{ContinueProcessing, ReturnWithResponse}

import com.gu.newproduct.api.addsubscription.Formatters._
import scala.concurrent.Future

object SendConfirmationEmailContributions extends Logging {
Expand Down Expand Up @@ -51,8 +50,6 @@ object SendConfirmationEmailContributions extends Logging {

def hyphenate(s: String) = s"${s.substring(0, 2)}-${s.substring(2, 4)}-${s.substring(4, 6)}"

def formatAmount(amount: AmountMinorUnits) = (amount.value / BigDecimal(100)).bigDecimal.stripTrailingZeros.toPlainString

val firstPaymentDateFormat = DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy")

def toContributionFields(currentDate: LocalDate, data: ContributionsEmailData): Option[ContributionFields] = {
Expand All @@ -65,7 +62,7 @@ object SendConfirmationEmailContributions extends Logging {
ContributionFields(
EmailAddress = email.value,
created = currentDate.toString,
amount = formatAmount(data.amountMinorUnits),
amount = data.amountMinorUnits.formatted,
currency = data.currency.glyph,
edition = data.billTo.country.map(_.alpha2).getOrElse(""),
name = data.billTo.firstName.value,
Expand Down
@@ -1,8 +1,8 @@
package com.gu.newproduct.api.addsubscription.validation

import com.gu.newproduct.api.addsubscription.ZuoraIds.ProductRatePlanId
import com.gu.newproduct.api.addsubscription.validation.Validation.BooleanValidation
import com.gu.newproduct.api.addsubscription.zuora.GetAccountSubscriptions.{Active, Subscription}
import com.gu.newproduct.api.productcatalog.ZuoraIds.ProductRatePlanId

object ValidateSubscriptions {
def apply(contributionRatePlanIds: List[ProductRatePlanId])(subscriptions: List[Subscription]): ValidationResult[List[Subscription]] = {
Expand Down
Expand Up @@ -2,9 +2,8 @@ package com.gu.newproduct.api.addsubscription.zuora

import java.time.LocalDate
import java.time.format.DateTimeFormatter

import com.gu.newproduct.api.addsubscription.ZuoraIds.{ProductRatePlanChargeId, ProductRatePlanId}
import com.gu.newproduct.api.addsubscription._
import com.gu.newproduct.api.productcatalog.ZuoraIds.{ProductRatePlanChargeId, ProductRatePlanId}
import com.gu.util.resthttp.ClientFailableOpLogging.LogImplicit2
import com.gu.util.resthttp.RestRequestMaker.{RequestsPost, WithCheck}
import com.gu.util.resthttp.Types.ClientFailableOp
Expand Down
@@ -1,7 +1,7 @@
package com.gu.newproduct.api.addsubscription.zuora

import com.gu.newproduct.api.addsubscription.ZuoraAccountId
import com.gu.newproduct.api.addsubscription.ZuoraIds.ProductRatePlanId
import com.gu.newproduct.api.productcatalog.ZuoraIds.ProductRatePlanId
import com.gu.util.resthttp.RestRequestMaker.{RequestsGet, WithCheck}
import com.gu.util.resthttp.Types.ClientFailableOp
import play.api.libs.json.Json
Expand Down
Expand Up @@ -37,7 +37,6 @@ case class Catalog(

object NewProductApi {
val catalog: Catalog = {
def monthlyPayment(priceInPounds: String) = Some(PaymentPlan(s"£$priceInPounds every month"))

val voucherWindowRule = WindowRule(
maybeCutOffDay = Some(DayOfWeek.TUESDAY),
Expand All @@ -59,16 +58,16 @@ object NewProductApi {
val monthlyContributionRules = StartDateRules(windowRule = Some(monthlyContributionWindow))

Catalog(
voucherWeekendPlus = Plan(VoucherWeekendPlus, voucherSaturdayDateRules, monthlyPayment("29.42")),
voucherWeekend = Plan(VoucherWeekend, voucherSaturdayDateRules, monthlyPayment("20.76")),
voucherSixDay = Plan(VoucherSixDay, voucherMondayRules, monthlyPayment("41.12")),
voucherSixDayPlus = Plan(VoucherSixDayPlus, voucherMondayRules, monthlyPayment("47.62")),
voucherEveryDay = Plan(VoucherEveryDay, voucherMondayRules, monthlyPayment("47.62")),
voucherEveryDayPlus = Plan(VoucherEveryDayPlus, voucherMondayRules, monthlyPayment("51.96")),
voucherSaturday = Plan(VoucherSaturday, voucherSaturdayDateRules, monthlyPayment("10.36")),
voucherSaturdayPlus = Plan(VoucherSaturdayPlus, voucherSaturdayDateRules, monthlyPayment("21.62")),
voucherSunday = Plan(VoucherSunday, voucherSundayDateRules, monthlyPayment("10.79")),
voucherSundayPlus = Plan(VoucherSundayPlus, voucherSundayDateRules, monthlyPayment("22.06")),
voucherWeekendPlus = Plan(VoucherWeekendPlus, voucherSaturdayDateRules),
voucherWeekend = Plan(VoucherWeekend, voucherSaturdayDateRules),
voucherSixDay = Plan(VoucherSixDay, voucherMondayRules),
voucherSixDayPlus = Plan(VoucherSixDayPlus, voucherMondayRules),
voucherEveryDay = Plan(VoucherEveryDay, voucherMondayRules),
voucherEveryDayPlus = Plan(VoucherEveryDayPlus, voucherMondayRules),
voucherSaturday = Plan(VoucherSaturday, voucherSaturdayDateRules),
voucherSaturdayPlus = Plan(VoucherSaturdayPlus, voucherSaturdayDateRules),
voucherSunday = Plan(VoucherSunday, voucherSundayDateRules),
voucherSundayPlus = Plan(VoucherSundayPlus, voucherSundayDateRules),
monthlyContribution = Plan(MonthlyContribution, monthlyContributionRules)
)
}
Expand Down Expand Up @@ -117,9 +116,7 @@ object PlanId {
def fromName(name: String): Option[PlanId] = supported.find(_.name == name)
}

case class Plan(id: PlanId, startDateRules: StartDateRules = StartDateRules(), paymentPlan: Option[PaymentPlan] = None)

case class PaymentPlan(description: String)
case class Plan(id: PlanId, startDateRules: StartDateRules = StartDateRules())

case class DelayDays(value: Int) extends AnyVal

Expand Down
Expand Up @@ -3,24 +3,32 @@ package com.gu.newproduct.api.productcatalog
import java.io.{InputStream, OutputStream}

import com.amazonaws.services.lambda.runtime.Context
import com.gu.effects.{GetFromS3, RawEffects}
import com.gu.newproduct.api.productcatalog.WireModel._
import com.gu.util.Logging
import com.gu.util.apigateway.ApiGatewayHandler.{LambdaIO, Operation}
import com.gu.util.apigateway.{ApiGatewayHandler, ApiGatewayRequest, ApiGatewayResponse}
import com.gu.util.reader.Types.ApiGatewayOp.ContinueProcessing
import com.gu.util.config.LoadConfigModule.StringFromS3
import com.gu.util.config.{Stage, ZuoraEnvironment}
import com.gu.util.reader.Types.ApiGatewayOp
import com.gu.newproduct.api.addsubscription.TypeConvert._

object Handler extends Logging {

val wireCatalog = WireCatalog.fromCatalog(NewProductApi.catalog)

// Referenced in Cloudformation
def apply(inputStream: InputStream, outputStream: OutputStream, context: Context): Unit =
ApiGatewayHandler(LambdaIO(inputStream, outputStream, context)) {
ContinueProcessing(
Operation.noHealthcheck {
Req: ApiGatewayRequest => ApiGatewayResponse(body = wireCatalog, statusCode = "200")
}
)
runWithEffects(LambdaIO(inputStream, outputStream, context), RawEffects.stage, GetFromS3.fetchString)
}

def runWithEffects(lambdaIO: LambdaIO, stage: Stage, fetchString: StringFromS3): ApiGatewayOp[Operation] = for {
zuoraIds <- ZuoraIds.zuoraIdsForStage(stage)
zuoraToPlanId = zuoraIds.voucherZuoraIds.zuoraIdToPlanid.get _
zuoraEnv = ZuoraEnvironment.EnvForStage(stage)
plansWithPrice <- PricesFromZuoraCatalog(zuoraEnv, fetchString, zuoraToPlanId).toApiGatewayOp("get prices from zuora catalog")
wireCatalog = WireCatalog.fromCatalog(NewProductApi.catalog, plansWithPrice.get)
} yield Operation.noHealthcheck {
Req: ApiGatewayRequest => ApiGatewayResponse(body = wireCatalog, statusCode = "200")
}
}

@@ -0,0 +1,104 @@
package com.gu.newproduct.api.productcatalog

import com.gu.newproduct.api.addsubscription.TypeConvert._
import com.gu.newproduct.api.productcatalog.ZuoraIds.ProductRatePlanId
import com.gu.util.config.LoadConfigModule.{S3Location, StringFromS3}
import com.gu.util.config.ZuoraEnvironment
import com.gu.util.resthttp.Types.ClientFailableOp
import play.api.libs.json.Json
import ZuoraCatalogWireModel._
import com.gu.newproduct.api.addsubscription.AmountMinorUnits

import scala.util.Try

case class PlanWithPrice(planId: PlanId, priceMinorUnits: AmountMinorUnits)

object ZuoraCatalogWireModel {

case class Price(price: Option[Double], currency: String)

object Price {
implicit val reads = Json.reads[Price]
}

case class RateplanCharge(
pricing: List[Price]
)

object RateplanCharge {
implicit val reads = Json.reads[RateplanCharge]
}

def toMinorUnits(amount: Double) = AmountMinorUnits((amount * 100).toInt)

case class Rateplan(
id: String,
productRatePlanCharges: List[RateplanCharge]
) {
def toParsedPlan(planIdFor: ProductRatePlanId => Option[PlanId]): Option[PlanWithPrice] = {
val maybePlanId = planIdFor(ProductRatePlanId(id))

maybePlanId flatMap { planId =>

val allPrices = for {
charge <- productRatePlanCharges
price <- charge.pricing
} yield price

val gbpAmounts = allPrices.collect { case Price(Some(amount), "GBP") => amount }
val totalPrice = if (gbpAmounts.isEmpty) None else Some(toMinorUnits(gbpAmounts.sum))
totalPrice.map(price => PlanWithPrice(planId, price))
}
}
}

object Rateplan {
implicit val reads = Json.reads[Rateplan]

}

case class Product(
id: String,
productRatePlans: List[Rateplan]
)

object Product {
implicit val reads = Json.reads[Product]
}

case class ZuoraCatalog(
products: List[Product]
) {
def toParsedPlans(planIdFor: ProductRatePlanId => Option[PlanId]): Map[PlanId, AmountMinorUnits] = {
val plansWithPrice = for {
product <- products
rateplan <- product.productRatePlans
parsedPlan <- rateplan.toParsedPlan(planIdFor).toList
} yield parsedPlan
plansWithPrice.map(x => x.planId -> x.priceMinorUnits).toMap
}
}

object ZuoraCatalog {
implicit val reads = Json.reads[ZuoraCatalog]
}
}

object PricesFromZuoraCatalog {

def apply(
zuoraEnvironment: ZuoraEnvironment,
fetchString: StringFromS3,
planIdFor: ProductRatePlanId => Option[PlanId]
): ClientFailableOp[Map[PlanId, AmountMinorUnits]] = {

val tryPrices = for {
catalogString <- fetchString(S3Location(bucket = "gu-zuora-catalog", key = s"PROD/Zuora-${zuoraEnvironment.value}/catalog.json"))
jsonCatalog <- Try(Json.parse(catalogString))
wireCatalog <- Try(jsonCatalog.as[ZuoraCatalog])
parsed = wireCatalog.toParsedPlans(planIdFor)
} yield parsed

tryPrices.toClientFailable(action = "get prices from zuora")
}
}

0 comments on commit bcefaea

Please sign in to comment.