Created
September 3, 2017 19:32
-
-
Save Kazanir/9d3173f970a035d220f3690d7ffaee94 to your computer and use it in GitHub Desktop.
Commerce License Billing 1.x Notes
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
Commerce License Billing - Current Layout | |
- CL Billing Cycle Engine | |
- Plugin Type | |
- Method: getBillingCycle($user, $start_date) | |
- Method: getNextBillingCycle($currentBillingCycle) | |
- Responsible for generating a series of billing cycles which determine what | |
time periods are used to e.g. pro-rate line items or generate new orders. | |
- This was fairly brittle in 1.x -- various untested assumptions essentially | |
depended on the periodic model. | |
- Functions as a bundle (via Entity Bundle Plugin) for Billing Cycle Types | |
- CL Billing Cycle Type | |
- Exportable(?) entity | |
- Serves as a bundle for Billing Cycles (the per-user, per-order cycle that | |
handles renewals, pro-rating, etc.) | |
- Configuration from the engine plugin supplies config options (i.e. whether | |
the billing cycle type is monthly or weekly, pre-paid or post-paid, etc. | |
- Products point to a specific billing cycle type to declare that the | |
licenses from those products should be attached to recurring billing based | |
on that cycle type. | |
- CL Billing Cycle | |
- Entity which tracks renewal and processing of recurring orders. | |
- Used to determine the start and end time of the cycle by pro-rating logic | |
(in a hard-coded way.) | |
- Status of the billing cycle determines which order actions need to be queued | |
for processing on the next cron run. Cron run schedules jobs to both close | |
and renew the cycle if its end has passed. | |
- CL Billing Usage Group | |
- Plugin which loads database usage records and returns information about them given a license and billing cycle | |
- Plugin Type: | |
- Method: enforceChangeScheduling: Enforces whether plans can be changed mid-cycle or not. | |
- Method: addUsage: Add a usage record (normally a quantity of usage) for a given license revision and start/end timestamp | |
- Method: currentUsage: Look at the current usage for a given billing cycle. | |
- Method: usageHistory: Get the usage records from a license for a billing cycle | |
- Method: chargeableUsage: Return the usage records for a given billing cycle | |
- Method: isComplete: Checks whether all usage records are complete for a given billing cycle. | |
- Method: onRevisionChange: Used to respond to revision changes by inserting new records to match the new license revision | |
Function/Class/Method list: | |
clb_menu: Declares an analyze order page for use in AJAX callbacks for Views Megarow. | |
clb_entity_info: UI for billing cycle types. | |
clb_entity_info_alter: Recurring order type. | |
clb_commerce_line_item_type_info: Recurring line item type. | |
clb_cycle_type_access: View all cycles, other actions admin only | |
clb_commerce_order_state_info: Order states for recurring orders | |
clb_cron: Look for unclosed billing cycles and queue their close/renewal | |
clb_advanced_queue_info: Set up queue worker jobs to process billing | |
clb_cycle_renew_queue_process: | |
- Get all licenses from an order | |
- Run all scheduled changes in the list for each license | |
- Get the next billing cycle | |
- Renew all licenses | |
- Generate a new recurring order for the new cycle and save it | |
clb_cycle_close_queue_process | |
- Move the cycle workflow to closed | |
- Get all licenses from the order | |
- Check if the order can be closed by checking for complete usage records | |
against the usage group and billing cycle for each license. | |
- If the order can be closed, move its status to payment_pending | |
- Delete no-longer-relevant history records if requested | |
- Otherwise change the status to usage_pending | |
clb_get_recurring_order_licenses: Get all the licenses 'attached' to an order. | |
clb_create_recurring_order: | |
- Examine a cart order and get the licenses attached to each line item | |
- Group those licenses according to the billing cycle type of their respective | |
products, if any | |
- For each billing cycle type, get/generate a billing cycle | |
- Make sure each license in the group doesn't expire automatically | |
- Generate a recurring order from the cart order and license list | |
- Take an initial cart order (either cart or prior-recurring) and generate a new recurring order | |
clb_generate_recurring_order: | |
- Look to see if the billing cycle for which an order is being requested, has | |
one | |
- If not, generate a shell order by copying all customer profile fields from | |
the previous order to a new one | |
- Allow other processes to alter this process so that other fields can be | |
mapped if necessary | |
- For each license we want to attach, generate a base line item | |
- Attach all these line items to the order and save it | |
clb_generate_base_line_item: | |
- Create a line item with the product and start/end field times of the license | |
product and billing cycle parameters | |
- Price the line item | |
- NOTE: Attaching a base recurring line item with a license to a recurring order is how the order knows a license is attached to it. These base line items don't use the same charge logic as the actual plan/usage history functions that run during order refresh, but that's because it is enough to just get the license on here and let the refresh take care of it. | |
clb_refresh_queue_process: | |
- Queue job to refresh an order rather than doing it on load | |
- Used for large orders that cannot refresh easily during a page request | |
- Tricky to implement and get right and the cause of various possible | |
consistency bugs | |
clb_invoke_pricing_event: | |
- The default line item pricing event is through rules, but was invoked by CLB | |
in a hard-coded fashion | |
- To get around this, we allowed users to override the pricing function and | |
call their own Rule, hook, or callback instead | |
- This is the wrapper function | |
clb_commerce_order_load: | |
- Used to refresh recurring orders when they are loaded | |
- Has a bunch of additional logic to manage queued or delayed refreshes | |
clb_order_refresh: | |
- The logical big daddy. This is where the action happens. | |
- Group up all licenses and line items on the current order for later comparison | |
- For each license: | |
- Collect the charges pertaining to the license and billing cycle | |
- Iterate over the existing line items looking for a matching charge | |
- If no line item is found, generate a new line item for this charge | |
- Otherwise create a line item by repopulating a clone of the existing line item | |
- This function applies the necessary values from the charge object to the line item but doesn't regenerate the rest of the entity | |
- If the cloned and repopulated line item matches on all key values then the old line item is kept | |
- Otherwise, the new line item is kept and assigned to the order's line items array | |
- After each license has collected its charges and added them to the desired line item array, allow other modules to alter this process to attach e.g. order-level discounts | |
- Compare the old and new line item IDs and determine which need to be deleted | |
- Save the new line items array onto the order. Yay, done! | |
clb_line_item_new: Create a line item shell to be populated with a charge's values | |
clb_populate_line_item: | |
- Take a line item entity and populate it with values matching the given charge object | |
- (A charge is generated by a plan or usage record and contains matching logic to make sure that orders can be refreshed with a minimum of fuss) | |
- Assign the following from the charge onto the line item: | |
- Label | |
- Product | |
- Quantity | |
- Unit price | |
- Start/End date | |
- Price the line item | |
clb_collect_charges: | |
- Get all the charges for a given license and billing cycle | |
- For prepaid charges, check if the license is scheduled for cancellation | |
- If yes, then we don't need to add charges to this order because those plan charges are for NEXT month, by which time the order will already be gone. | |
- Assemble a charge (this is hard-coded and not pluggable) from the license history record | |
- If the license is postpaid, then get the list of plan history charges instead here (because post-paid plans can change mid-cycle) | |
- For each usage group on the license, if any: | |
- Get all the charges for the group and add them to the overall charge list | |
- Once this is all done, allow other modules to alter the list of charges. | |
clb_estimate_cost: | |
- Try to construct enough information to price a product as if it were part of a recurring order | |
- Assemble a charges array (much like in the collect_charges function) based on a shell license and the usage information passed to the function | |
- Strip down the charges array to a list of charges and a total price and return it | |
clb_calculate_sell_price: | |
- Generate a recurring line item shell for the given product and populate it | |
- Price the line item and return its price | |
clb_get_license_billing_cycle: Wrapper function to load the most current billing lcycle associated with a given license | |
clb_commerce_license_presave: Ensures that a new revision is requested whenever a license's product or status is changed, as this affects licenses on post-paid billing plans | |
clb_commerce_license_update: Ensure that usage groups are notified when a revision change occurs | |
clb_commerce_license_delete: Delete usage records for licenses which are deleted. (This function is sort of dumb and useless because licenses with usage records are never deleted but rather only revoked.) | |
clb_commerce_order_delete: Delete the attached billing cycle when an order is deleted | |
clb_change_plan: Change a plan, optionally scheduling the change instead based on the product settings. | |
clb_change_status: Same thing as above, but for status. Schedule change if necessarily, otherwise change and sync. | |
clb_must_schedule_change: | |
- Check whether or not the billing type of the product is prepaid or postpaid | |
- Prepaids must always schedule licenses. | |
- Allow usage groups to enforce scheduling in case they cannot pro-rate accurately for some reason | |
- Check for the same setting at the product level | |
clb_schedule_change: | |
- Insert a DB record for the scheduled change | |
- Simple record that updates a single license property to a value | |
clb_schedule_changes_list: Gets the list of scheduled changes for a license | |
clb_apply_changes: Applies a set of scheduled change records to a license | |
clb_plan_history_list: | |
- Look up the plan history records for a given license and billing cycle | |
- Remove the non-active revision records and massage them to start and end with the billing cycle if necessary | |
- Cache the results and return the needful array of records | |
clb_usage_history_clear: Get rid of the usage records for a license and billing cycle if cleanup is desired | |
clb_usage_group: Get the correct plugin for a given usage group on a license and return it | |
clb_field_access: Don't allow editing of the billing cycle field on any entity. | |
clb_configure_product_types: Ensure that various reference fields on products pertaining to recurring configuration | |
clb_configure_line_item_type: Set up the recurring line item type with appropriate fields | |
clb_configure_order_type: Set up the recurring order type with appropriate fields | |
clb_field_widget_form_alter: Hide fields from products if no billing cycle type is selected | |
Rules Conditions: | |
clb_product_eligible: Check whether a product has a billing cycle type and thus should invoke the recurring process at checkout | |
clb_order_eligible: Check whether a checkout order can be used to spawn a new recurring order -- applies if any product in the checked out items has a billing cycle type | |
clb_product_prorating_eligible_condition: Check whether a cart product should be pro-rated due to recurring billing -- applies to usage and postpaid-plan products | |
Rules Actions: | |
clb_create_recurring_orders: Create recurring orders based on a checked-out order | |
clb_prorate_product_line_item: Pro-rate a cart order's line item for recurring reasons | |
clb_prorate_recurring_line_item: Pro-rate a recurring line item based on its billing info | |
clb_prorate_line_item: Prorate a line item and billing cycle against a duration parameter. Internal API callback. | |
Rules Defaults: | |
clb_default_rules_configuration_alter: Ensure that new account creation runs at order-paid time not checkout_complete time so that licenses can open recurring orders appropriately. | |
clb_default_rules_configuration: | |
- clb_set_postpaid_product_price: Set the price of post-paid billing type products in a cart order to 0. | |
- clb_prorate_prepaid_product_price: Invokes the prorate_product action to pro-rate a given prepaid-type product | |
- clb_prorate_recurring_line_item: Respond to commerce_product_calculate_sell_price by pro-rating recurring line items correctly | |
- clb_create_recurring_orders: When a cart order is checked out and paid in full, create recurring orders from the checked-out order if eligible. | |
- rules_clb_charge_recurring_order: When an order is moved to recurring_payment_pending status, charge it via the nearest card on file if its balance is not 0 | |
- rules_clb_update_order_charged: Update an order to completed if it is successfully charged for | |
clb.install | |
Billing cycle fields: | |
- id | |
- type | |
- owner | |
- index by | |
- start | |
- end | |
- status (state) | |
Billing cycle type fields: | |
- id | |
- engine | |
- name | |
- title | |
- status (exportability) | |
- module (exportability) | |
Usage group fields: | |
- id | |
- license | |
- license_revision | |
- group_name | |
- quantity | |
- start | |
- end | |
Schedule change fields | |
- id | |
- license | |
- property (to change) | |
- value (to change to) | |
- created (this record was at) | |
api.php | |
hook_clb_initial_usage: Get the initial usage for a usage group, defaulting to a value from group_info. (Don't know WHY this wasn't possibly a CLASS METHOD...) | |
hook_clb_estimation_alter: Alter the estimated charges for a given requested product's estimation. Used to mimic order-level logic like e.g. coupons or sales tax. | |
hook_clb_new_recurring_order_alter: See above, but for altering new recurring orders. This is usually used to transport more fields from a previous order, or alter something about the renewal process. | |
hook_clb_order_refresh_alter: See estimation_alter, but for actual recurring orders and attaching order-level fees and discounts. | |
hook_clb_refresh_deny: Skip refreshing an order. Used in the complicated caching/queueing logic that chooses when to refresh a loaded order and when (for very large orders) to skip it | |
clb.theme.inc | |
- Themes an estimation table for a product estimation. | |
includes/views/clb_handler_usage_details.inc | |
- Handler to render usage details of a given license based on its usage groups | |
includes/views/clb_views.inc | |
- Adds the computed usage details field handler to the license base table generated by the entity info integration with Views | |
includes/views/clb_views_default.inc | |
- A sort of bad default recurring billing dashboard for recurring orders | |
includes/clb.cycle.inf | |
- The billing cycle entity | |
includes/clb.cycle_type_ui.inc | |
- UI for adding billing cycle types based on the available engine plugins | |
- This should be handled by entity traits on the CLBCType form fields automagically in 2.x | |
interface/clb.interface.inc | |
- Interface licenses can adopt to say they are implenting usage-driven charges in its recurring billing config | |
includes/clb.pages.inc | |
- Pages for various administration tasks | |
clb_analyze_page: Build a license details and history pages for the given order, showing all licenses and their usage histories | |
clb_build_usage_status: Check out where usage records are missing for a given license and billing cycle | |
clb_build_usage_table: Build a usage table of all usage records for a given revision, license, and billing cycle | |
plugins/billing_cycle_engine/CLBCycleTypePeriodic.class.php | |
- The periodic billing engine class | |
- Fields: | |
- Period: Select hour/day/week/etc | |
- Async: Whether the BCType is synchronous or asynchronous | |
- Based on these 2 fields, the getBillingCycle/getNextBillingCycle will either sync themselves up to the start of the respective periods (pro-rating things bought during the middle of the cycle) or act more like a recurring Netflix subscription where the billing is based on your start date. | |
- Note: This leaves the interesting edge case where a user could end up with 2 products on the same asynchronous recurring order if they were checked out at a timestamp which is exactly [time period] offset from another billingl cycle. Hasn't happened yet, but... | |
plugins/usage_group/base.inc | |
CLBUsageGroupBase | |
::__construct: Inject the license and group info. | |
::enforceChangeScheduling: Nope. | |
::addUsage: Add the raw usage quantity as requested, unless the revision is inactive. | |
::usageHistory: Return the raw usage history records | |
::isComplete: Enforce that records must add up to each revision's length. This is really a gauge-specific implementation that ended up here...this should be either abstract or just return TRUE and let the indiviudal plugins implement since it is highly plugin-specific. | |
::generateCharge: Create a new charge object (with matching and line-item-generation logic) for a usage record. | |
::onRevisionChange: Nothing, which is weird in light of the isComplete implementation above. | |
::freeQuantities: Compare the products on each revision ID and return the list of free quantities for each revision and this usage group | |
plugins/usage_group/CLBCounterUsageGroup.class.php | |
CLBCounterUsageGroup | |
::addUsage: Overridden to just pick up the current time as start/end if nothing is passed in, and then open the new usage record. | |
::currentUsage: Add up the usage for the current revision ID of the license | |
::chargeableUsage: Pro-rate the free quantities from each revision and subtract them from the summed-up counter records from each revision. Return each completed record's charge. | |
::isComplete: Depending on the group settings, always return TRUE for immediate-record usage groups. Due to the base class' bad implementation here, this actually means that counter usage groups that DON'T set the "immediate" property have been broken in CLB 1.x since...forever. | |
plugins/usage_group/CLBGaugeUsageGroup.class.php | |
CLBGaugeUsageGroup | |
::addUsage: Get the previous usage group and set the other usage records for this revision ID to have starts/ends that match up. This essentially allows the new usage record to "take its place" in the timeline, with other usage records adjusting around it so that there is no overlap. With that done, the parent class is called to add the actual new record. | |
::currentUsage: Get the current usage record (i.e. the most recent one for the revision ID.) With the fixed/improved implementation of addUsage that we patched recently, I realize that this method is also broken as it orders by usage_id rather than finding the most recent record which contains the current timestamp. Ugh. | |
::chargeableUsage: Get every usage record and generate a charge for it, deducting free quantities from each by revision ID of the license. Interestingly, this class allows a group to skip being charged in a way that the counter class for some reason does not. What gives, 2013? | |
::onRevisionChange: When a revision is changed, we need to update the current record to end with the previous revision and insert a new record to start with the new revision | |
::initialUsage: Get the initial quantity from the group info if there is no hook implementation. Why is this in the Gauge class instead of everywhere? Probably because onRevisionChange uses it? I'm so confused by this. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment