diff --git a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/Handler.scala b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/Handler.scala index c26eae3ef3..69792802b0 100644 --- a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/Handler.scala +++ b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/Handler.scala @@ -27,7 +27,7 @@ import com.gu.newproduct.api.addsubscription.zuora.GetContacts.BillToContact import com.gu.newproduct.api.addsubscription.zuora.GetContacts.WireModel.GetContactsResponse 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.PlanId.{AnnualContribution, MonthlyContribution} import com.gu.newproduct.api.productcatalog.ZuoraIds.{PlanAndCharge, ProductRatePlanId} import com.gu.newproduct.api.productcatalog._ import com.gu.util.Logging @@ -89,7 +89,8 @@ object Steps { paymentMethod = paymentMethod, amountMinorUnits = amountMinorUnits, firstPaymentDate = firstPaymentDate, - billTo = billToContact + billTo = billToContact, + planId = request.planId ) def handleRequest( @@ -100,13 +101,13 @@ object Steps { ): Future[ApiResponse] = (for { request <- apiGatewayRequest.bodyAsCaseClass[AddSubscriptionRequest]().withLogging("parsed request").toAsync subscriptionName <- request.planId match { - case MonthlyContribution => addContribution(request) + case MonthlyContribution | AnnualContribution => addContribution(request) case _ => addVoucher(request) } } yield ApiGatewayResponse(body = AddedSubscription(subscriptionName.value), statusCode = "200")).apiResponse def addContributionSteps( - contributionZuoraIds: PlanAndCharge, + getPlanAndCharge: PlanId => Option[PlanAndCharge], getCustomerData: ZuoraAccountId => ApiGatewayOp[ContributionCustomerData], contributionValidations: (ValidatableFields, Currency) => ValidationResult[AmountMinorUnits], createSubscription: ZuoraCreateSubRequest => ClientFailableOp[SubscriptionName], @@ -118,8 +119,9 @@ object Steps { validatableFields = ValidatableFields(request.amountMinorUnits, request.startDate) amountMinorUnits <- contributionValidations(validatableFields, account.currency).toApiGatewayOp.toAsync acceptanceDate = request.startDate.plusDays(paymentDelayFor(paymentMethod)) - chargeOverride = ChargeOverride(amountMinorUnits, contributionZuoraIds.productRatePlanChargeId) - zuoraCreateSubRequest = createZuoraSubRequest(request, acceptanceDate, Some(chargeOverride), contributionZuoraIds.productRatePlanId) + planAndCharge <- getPlanAndCharge(request.planId).toApiGatewayContinueProcessing(internalServerError(s"no Zuora id for ${request.planId}!")).toAsync + chargeOverride = ChargeOverride(amountMinorUnits, planAndCharge.productRatePlanChargeId) + zuoraCreateSubRequest = createZuoraSubRequest(request, acceptanceDate, Some(chargeOverride), planAndCharge.productRatePlanId) subscriptionName <- createSubscription(zuoraCreateSubRequest).toAsyncApiGatewayOp("create monthly contribution") contributionEmailData = toContributionEmailData(request, account.currency, paymentMethod, acceptanceDate, contacts.billTo, amountMinorUnits) _ <- sendConfirmationEmail(account.sfContactId, contributionEmailData).recoverAndLog("send contribution confirmation email") @@ -194,7 +196,8 @@ object Steps { validateRequest = ContributionValidations(isValidContributionStartDate, AmountLimits.limitsFor) _ sendConfirmationEmail = SendConfirmationEmailContributions(contributionEtSqsSend, getCurrentDate) _ - contributionSteps = addContributionSteps(zuoraIds.contributionsZuoraIds.monthly, getCustomerData, validateRequest, createSubscription, sendConfirmationEmail) _ + planAndChargeForContributionPlanId = zuoraIds.contributionsZuoraIds.byApiPlanId.get _ + contributionSteps = addContributionSteps(planAndChargeForContributionPlanId, getCustomerData, validateRequest, createSubscription, sendConfirmationEmail) _ voucherSqsSend = awsSQSSend(queueNames.voucher) voucherEtSqsSend = EtSqsSend[VoucherEmailData](voucherSqsSend) _ sendVoucherEmail = SendConfirmationEmailVoucher(voucherEtSqsSend, getCurrentDate) _ diff --git a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributions.scala b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributions.scala index 0f64083f62..0cfd5a8ca1 100644 --- a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributions.scala +++ b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributions.scala @@ -14,7 +14,8 @@ import com.gu.util.reader.AsyncTypes._ import com.gu.util.reader.Types.ApiGatewayOp.{ContinueProcessing, ReturnWithResponse} import com.gu.newproduct.api.addsubscription.Formatters._ import com.gu.newproduct.api.addsubscription.zuora.GetAccount.SfContactId -import com.gu.newproduct.api.productcatalog.AmountMinorUnits +import com.gu.newproduct.api.productcatalog.PlanId.{AnnualContribution, MonthlyContribution} +import com.gu.newproduct.api.productcatalog.{AmountMinorUnits, PlanId} import scala.concurrent.Future @@ -26,7 +27,8 @@ object SendConfirmationEmailContributions extends Logging { paymentMethod: PaymentMethod, amountMinorUnits: AmountMinorUnits, firstPaymentDate: LocalDate, - billTo: BillToContact + billTo: BillToContact, + planId: PlanId ) def apply( @@ -60,6 +62,11 @@ object SendConfirmationEmailContributions extends Logging { def toContributionFields(currentDate: LocalDate, data: ContributionsEmailData): Option[ContributionFields] = { + val productId = data.planId match { + case AnnualContribution => "annual-contribution" + case MonthlyContribution => "monthly-contribution" + case other => other.name + } val maybeDirectDebit = data.paymentMethod match { case d: DirectDebit => Some(d) case _ => None @@ -72,7 +79,7 @@ object SendConfirmationEmailContributions extends Logging { currency = data.currency.glyph, edition = data.billTo.address.country.map(_.alpha2).getOrElse(""), name = data.billTo.firstName.value, - product = "monthly-contribution", + product = productId, `account name` = maybeDirectDebit.map(_.accountName.value), `account number` = maybeDirectDebit.map(_.accountNumberMask.value), `sort code` = maybeDirectDebit.map(_.sortCode.hyphenated), diff --git a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/Catalog.scala b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/Catalog.scala index fdb505dba2..1633682e2d 100644 --- a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/Catalog.scala +++ b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/Catalog.scala @@ -13,7 +13,8 @@ case class Catalog( voucherSundayPlus: Plan, voucherEveryDayPlus: Plan, voucherSixDayPlus: Plan, - monthlyContribution: Plan + monthlyContribution: Plan, + annualContribution: Plan ) { val allPlans = List( voucherWeekend, @@ -26,7 +27,8 @@ case class Catalog( voucherSundayPlus, voucherEveryDayPlus, voucherSixDayPlus, - monthlyContribution + monthlyContribution, + annualContribution ) val planForId: Map[PlanId, Plan] = allPlans.map(x => x.id -> x).toMap @@ -35,6 +37,7 @@ case class Catalog( sealed abstract class PlanId(val name: String) object PlanId { + case object AnnualContribution extends PlanId("annual_contribution") case object MonthlyContribution extends PlanId("monthly_contribution") @@ -60,6 +63,7 @@ object PlanId { val supported = List( MonthlyContribution, + AnnualContribution, VoucherWeekend, VoucherEveryDay, VoucherSixDay, diff --git a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/NewProductApi.scala b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/NewProductApi.scala index b5a9f9edc3..7e280fe458 100644 --- a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/NewProductApi.scala +++ b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/NewProductApi.scala @@ -29,7 +29,7 @@ object NewProductApi { maybeCutOffDay = None, maybeStartDelay = None ) - val monthlyContributionRules = StartDateRules(windowRule = Some(monthlyContributionWindow)) + val contributionRules = StartDateRules(windowRule = Some(monthlyContributionWindow)) def planWithPayment( planId: PlanId, @@ -48,7 +48,8 @@ object NewProductApi { voucherSaturdayPlus = planWithPayment(VoucherSaturdayPlus, PlanDescription("Saturday+"), voucherSaturdayDateRules), voucherSunday = planWithPayment(VoucherSunday, PlanDescription("Sunday"), voucherSundayDateRules), voucherSundayPlus = planWithPayment(VoucherSundayPlus, PlanDescription("Sunday+"), voucherSundayDateRules), - monthlyContribution = planWithPayment(MonthlyContribution, PlanDescription("Monthly"), monthlyContributionRules) + monthlyContribution = planWithPayment(MonthlyContribution, PlanDescription("Monthly"), contributionRules), + annualContribution = planWithPayment(AnnualContribution, PlanDescription("Annual"), contributionRules), ) } diff --git a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/WireModel.scala b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/WireModel.scala index 34a497fe9c..d346a70ef2 100644 --- a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/WireModel.scala +++ b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/WireModel.scala @@ -119,7 +119,8 @@ object WireModel { val contributionProduct = WireProduct( label = "Contribution", plans = List( - WirePlanInfo.fromPlan(catalog.monthlyContribution) + WirePlanInfo.fromPlan(catalog.monthlyContribution), + WirePlanInfo.fromPlan(catalog.annualContribution) ) ) WireCatalog(List(contributionProduct, voucherProduct)) diff --git a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/ZuoraIds.scala b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/ZuoraIds.scala index 85246b2cc5..39d1f1e926 100644 --- a/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/ZuoraIds.scala +++ b/handlers/new-product-api/src/main/scala/com/gu/newproduct/api/productcatalog/ZuoraIds.scala @@ -12,7 +12,12 @@ object ZuoraIds { case class PlanAndCharge(productRatePlanId: ProductRatePlanId, productRatePlanChargeId: ProductRatePlanChargeId) - case class ContributionsZuoraIds(monthly: PlanAndCharge, annual: PlanAndCharge) + case class ContributionsZuoraIds(monthly: PlanAndCharge, annual: PlanAndCharge) { + val byApiPlanId: Map[PlanId, PlanAndCharge] = Map( + MonthlyContribution -> monthly, + AnnualContribution -> annual + ) + } case class VoucherZuoraIds( everyday: ProductRatePlanId, diff --git a/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/ContributionStepsTest.scala b/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/ContributionStepsTest.scala index 95529b43d1..7d46d5ad99 100644 --- a/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/ContributionStepsTest.scala +++ b/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/ContributionStepsTest.scala @@ -10,7 +10,7 @@ import com.gu.newproduct.api.addsubscription.validation.{Failed, Passed} import com.gu.newproduct.api.addsubscription.zuora.CreateSubscription import com.gu.newproduct.api.addsubscription.zuora.CreateSubscription.{ChargeOverride, SubscriptionName, ZuoraCreateSubRequest} import com.gu.newproduct.api.addsubscription.zuora.GetAccount.SfContactId -import com.gu.newproduct.api.productcatalog.AmountMinorUnits +import com.gu.newproduct.api.productcatalog.{AmountMinorUnits, PlanId} import com.gu.newproduct.api.productcatalog.ZuoraIds.{PlanAndCharge, ProductRatePlanChargeId, ProductRatePlanId} import com.gu.test.JsonMatchers.JsonMatcher import com.gu.util.apigateway.ApiGatewayRequest @@ -36,6 +36,8 @@ class ContributionStepsTest extends FlatSpec with Matchers { ProductRatePlanChargeId("ratePlanChargeId") ) + def getPlanAndCharge(planId: PlanId) = Some(planAndCharge) + val expectedIn = ZuoraCreateSubRequest( planAndCharge.productRatePlanId, ZuoraAccountId("acccc"), @@ -79,7 +81,7 @@ class ContributionStepsTest extends FlatSpec with Matchers { val expectedOutput = ExpectedOut("well done") val fakeAddContributionSteps = Steps.addContributionSteps( - planAndCharge, + getPlanAndCharge, fakeGetCustomerData, fakeValidateRequest, fakeCreate, diff --git a/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributionsTest.scala b/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributionsTest.scala index d207ba2dcf..1d1f014ba8 100644 --- a/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributionsTest.scala +++ b/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/addsubscription/email/contributions/SendConfirmationEmailContributionsTest.scala @@ -12,10 +12,12 @@ import com.gu.newproduct.api.addsubscription.zuora.GetContacts._ import com.gu.newproduct.api.addsubscription.zuora.GetPaymentMethod.{BankAccountName, BankAccountNumberMask, DirectDebit, MandateId, NonDirectDebitMethod, SortCode} import com.gu.newproduct.api.addsubscription.zuora.PaymentMethodStatus.ActivePaymentMethod import com.gu.newproduct.api.addsubscription.zuora.PaymentMethodType.CreditCard -import com.gu.newproduct.api.productcatalog.AmountMinorUnits +import com.gu.newproduct.api.productcatalog.PlanId.{AnnualContribution, MonthlyContribution} +import com.gu.newproduct.api.productcatalog.{AmountMinorUnits, PlanId} import com.gu.util.apigateway.ApiGatewayResponse import com.gu.util.reader.Types.ApiGatewayOp.{ContinueProcessing, ReturnWithResponse} import org.scalatest.{AsyncFlatSpec, Matchers} + import scala.concurrent.Future class SendConfirmationEmailContributionsTest extends AsyncFlatSpec with Matchers { @@ -51,7 +53,8 @@ class SendConfirmationEmailContributionsTest extends AsyncFlatSpec with Matchers paymentMethod = directDebit, amountMinorUnits = AmountMinorUnits(1234), firstPaymentDate = LocalDate.of(2018, 8, 9), - billTo = testContact + billTo = testContact, + planId = MonthlyContribution ) val sfContactId = Some(SfContactId("sfContactId")) diff --git a/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/productcatalog/CatalogWireTest.scala b/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/productcatalog/CatalogWireTest.scala index 48e6d6c550..1581c22fe7 100644 --- a/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/productcatalog/CatalogWireTest.scala +++ b/handlers/new-product-api/src/test/scala/com/gu/newproduct/api/productcatalog/CatalogWireTest.scala @@ -22,6 +22,15 @@ class CatalogWireTest extends FlatSpec with Matchers { | "sizeInDays": 1 | } | } + | }, + | { + | "id": "annual_contribution", + | "label": "Annual", + | "startDateRules": { + | "selectableWindow": { + | "sizeInDays": 1 + | } + | } | } | ] | }, @@ -196,6 +205,7 @@ class CatalogWireTest extends FlatSpec with Matchers { case VoucherSixDay => Some(AmountMinorUnits(4112)) case VoucherSixDayPlus => Some(AmountMinorUnits(4762)) case MonthlyContribution => None + case AnnualContribution => None } val wireCatalog = WireCatalog.fromCatalog(NewProductApi.catalog(fakePricesFor)) diff --git a/handlers/new-product-api/src/test/scala/manualTest/SendConfirmationEmailsManualTest.scala b/handlers/new-product-api/src/test/scala/manualTest/SendConfirmationEmailsManualTest.scala index 40f8ca1fc1..4a9eb6f6b2 100644 --- a/handlers/new-product-api/src/test/scala/manualTest/SendConfirmationEmailsManualTest.scala +++ b/handlers/new-product-api/src/test/scala/manualTest/SendConfirmationEmailsManualTest.scala @@ -14,6 +14,7 @@ import com.gu.newproduct.api.addsubscription.zuora.{PaymentMethodStatus, Payment import com.gu.newproduct.api.addsubscription.ZuoraAccountId import com.gu.newproduct.api.addsubscription.zuora.GetAccount.SfContactId import com.gu.newproduct.api.productcatalog.AmountMinorUnits +import com.gu.newproduct.api.productcatalog.PlanId.MonthlyContribution import com.gu.util.config.Stage import scala.concurrent.Await @@ -42,7 +43,8 @@ object SendConfirmationEmailsManualTest { NonDirectDebitMethod(PaymentMethodStatus.ActivePaymentMethod, PaymentMethodType.PayPal), AmountMinorUnits(123), LocalDate.of(2018, 9, 1), - billtoContact + billtoContact, + MonthlyContribution ) val fakeDate = LocalDate.of(2018, 8, 10)