-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1087 from guardian/ph-20241022-1601-orders-amende…
…ment Orders Harmonization: Subscription Amendment
- Loading branch information
Showing
9 changed files
with
879 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
lambda/src/main/scala/pricemigrationengine/model/ZuoraAmendmentOrderPayload.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.