lambda/src/main/scala/pricemigrationengine/model/ZuoraAmendmentOrderPayload.scala (74 lines of code) (raw):

package pricemigrationengine.model import java.time.LocalDate import upickle.default._ /* Author: Pascal Comment id: 4eb4b0a9 This file was written in October 2024 when we introduced Zuora.applyAmendmentOrder with the aim of eventually decommissioning Zuora.updateSubscription. The documentation for this Order is available here: Zuora documentation: https://knowledgecenter.zuora.com/Zuora_Billing/Manage_subscription_transactions/Orders/Order_actions_tutorials/D_Replace_a_product_in_a_subscription The migration to the new Orders API is currently a work in progress and the choice of the model hierarchy in this file reflects the fact that we are migrating SupporterPlus2024 from the old API to the new Orders API; in other words migrating the amendment a SupporterPlus2024 subscription from using Zuora.updateSubscription to using Zuora.applyAmendmentOrder */ /* Author: Pascal Comment id: cada56ad The model hierarchy presented in this file captures the making of a ZuoraAmendmentOrderPayload according to the schema presented here: https://knowledgecenter.zuora.com/Zuora_Billing/Manage_subscription_transactions/Orders/Order_actions_tutorials/D_Replace_a_product_in_a_subscription One interesting note about the design is using a sealed trait to represent the different types of an Order action, in the context of replacing a product in a subscription. Indeed, the two actions in that order are: { "type": "RemoveProduct", "triggerDates": [ { "name": "ContractEffective", "triggerDate": "2024-11-28" }, { "name": "ServiceActivation", "triggerDate": "2024-11-28" }, { "name": "CustomerAcceptance", "triggerDate": "2024-11-28" } ], "removeProduct": { "ratePlanId": "8a12867e92c341870192c7c46bdb47d6" } } and { "type": "AddProduct", "triggerDates": [ { "name": "ContractEffective", "triggerDate": "2024-11-28" }, { "name": "ServiceActivation", "triggerDate": "2024-11-28" }, { "name": "CustomerAcceptance", "triggerDate": "2024-11-28" } ], "addProduct": { "productRatePlanId": "8a128ed885fc6ded018602296ace3eb8", "chargeOverrides": [ { "productRatePlanChargeId": "8a128ed885fc6ded018602296af13eba", "pricing": { "recurringFlatFee": { "listPrice": 12 } } }, { "productRatePlanChargeId": "8a128d7085fc6dec01860234cd075270", "pricing": { "recurringFlatFee": { "listPrice": 0 } } } ] } } They are respectively modeled as ZuoraAmendmentOrderPayloadOrderActionRemove and ZuoraAmendmentOrderPayloadOrderActionAdd Because of the way upickle works, we end up with a JSON serialization that has a "$type" field which is a bit annoying. We can see it in the test data in SupporterPlus2024MigrationTest. I have tried to submit the Orders payload to Zuora with the "$type" field left inside the JSON but Zuora errors during the processing. (Technically, it should actually work, but I think that Zuora may be doing payload introspection beyond the mandatory fields and is error'ing on the "$type" field) I have tried to remove the "$type" field by providing a custom writer for the serialization of two classes, but could not get that to work. In the end the solution I have chosen was to remove the "$type" field from the JSON before sending it to Zuora, which explains the function type_flush in the ZuoraLive implementation of applyAmendmentOrder This is, for all intent and purpose a hack and in a future change we may try and get those custom writers to work to avoid string manipulation in the implementation of applyAmendmentOrder. */ sealed trait ZuoraAmendmentOrderPayloadOrderAction object ZuoraAmendmentOrderPayloadOrderAction { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderAction] = Writer.merge( macroW[ZuoraAmendmentOrderPayloadOrderActionAdd], macroW[ZuoraAmendmentOrderPayloadOrderActionRemove] ) } case class ZuoraAmendmentOrderPayloadOrderActionTriggerDate(name: String, triggerDate: LocalDate) object ZuoraAmendmentOrderPayloadOrderActionTriggerDate { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderActionTriggerDate] = macroW } case class ZuoraAmendmentOrderPayloadOrderActionRemoveProduct(ratePlanId: String) object ZuoraAmendmentOrderPayloadOrderActionRemoveProduct { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderActionRemoveProduct] = macroW } case class ZuoraAmendmentOrderPayloadOrderActionRemove( `type`: String, triggerDates: List[ZuoraAmendmentOrderPayloadOrderActionTriggerDate], removeProduct: ZuoraAmendmentOrderPayloadOrderActionRemoveProduct ) extends ZuoraAmendmentOrderPayloadOrderAction object ZuoraAmendmentOrderPayloadOrderActionRemove { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderActionRemove] = macroW } case class ZuoraAmendmentOrderPayloadOrderActionAddProductChargeOverride( productRatePlanChargeId: String, pricing: Map[String, Map[String, BigDecimal]] ) object ZuoraAmendmentOrderPayloadOrderActionAddProductChargeOverride { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderActionAddProductChargeOverride] = macroW } case class ZuoraAmendmentOrderPayloadOrderActionAddProduct( productRatePlanId: String, chargeOverrides: List[ZuoraAmendmentOrderPayloadOrderActionAddProductChargeOverride] ) object ZuoraAmendmentOrderPayloadOrderActionAddProduct { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderActionAddProduct] = macroW } case class ZuoraAmendmentOrderPayloadOrderActionAdd( `type`: String, triggerDates: List[ZuoraAmendmentOrderPayloadOrderActionTriggerDate], addProduct: ZuoraAmendmentOrderPayloadOrderActionAddProduct ) extends ZuoraAmendmentOrderPayloadOrderAction object ZuoraAmendmentOrderPayloadOrderActionAdd { implicit val w: Writer[ZuoraAmendmentOrderPayloadOrderActionAdd] = macroW } case class ZuoraAmendmentOrderPayloadSubscription( subscriptionNumber: String, orderActions: List[ZuoraAmendmentOrderPayloadOrderAction] ) object ZuoraAmendmentOrderPayloadSubscription { implicit val w: Writer[ZuoraAmendmentOrderPayloadSubscription] = macroW } case class ZuoraAmendmentOrderPayloadProcessingOptions(runBilling: Boolean, collectPayment: Boolean) object ZuoraAmendmentOrderPayloadProcessingOptions { implicit val w: Writer[ZuoraAmendmentOrderPayloadProcessingOptions] = macroW } case class ZuoraAmendmentOrderPayload( orderDate: LocalDate, existingAccountNumber: String, subscriptions: List[ZuoraAmendmentOrderPayloadSubscription], processingOptions: ZuoraAmendmentOrderPayloadProcessingOptions ) object ZuoraAmendmentOrderPayload { implicit val w: Writer[ZuoraAmendmentOrderPayload] = macroW } case class ZuoraAmendmentOrderResponse( success: Boolean // Be careful if you are considering extending this class because the answer's shape // varies depending on whether the operation was successful or not. ) object ZuoraAmendmentOrderResponse { implicit val rw: ReadWriter[ZuoraAmendmentOrderResponse] = macroRW }