Our tests use hourly billing periods. This is a mistake, cause hours are always equal. Months are not.

So here's a problem not covered by tests:

  public function collectCharges(SubscriptionInterface $subscription, BillingPeriod $billing_period) {
    $start_date = $subscription->getStartDate();
    $billing_type = $subscription->getBillingSchedule()->getBillingType();
    if ($billing_type == BillingScheduleInterface::BILLING_TYPE_PREPAID) {
      // The subscription has either ended, or is scheduled for cancellation,
      // meaning there's nothing left to prepay.
      if ($subscription->getState()->getId() != 'active' ||
        $subscription->hasScheduledChange('state', 'canceled')) {
        return [];
      }
      $billing_schedule = $subscription->getBillingSchedule()->getPlugin();
      // The initial order (which starts the subscription) pays the first
      // billing period, so the base charge is always for the next one.
      // The October recurring order (ending on Nov 1st) charges for November.
      $base_billing_period = $billing_schedule->generateNextBillingPeriod($start_date, $billing_period);
    }

The $base_billing_period is set on the charge.
The $charge->getBillingPeriod() is always prorated against the order billing period.
So November is prorated against October. Which doesn't make any sense whatsoever.
We need to be able to say "don't prorate this charge".

Similar problem:

  public function collectTrialCharges(SubscriptionInterface $subscription, BillingPeriod $trial_period) {
    $start_date = $subscription->getStartDate();
    $billing_type = $subscription->getBillingSchedule()->getBillingType();
    if ($billing_type == BillingScheduleInterface::BILLING_TYPE_PREPAID) {
      $billing_schedule = $subscription->getBillingSchedule()->getPlugin();
      // The base charge for prepaid subscriptions always covers the next
      // period, which in the case of trials is the first billing period.
      $base_unit_price = $subscription->getUnitPrice();
      $base_billing_period = $billing_schedule->generateFirstBillingPeriod($start_date);
      $base_billing_period = $this->adjustBillingPeriod($base_billing_period, $subscription);
    }

Imagine a 14 day trial, with the trial period being Mar 1st - Mar 15th. The first billing period is Mar 1st - Mar 31st. The base charge needs to pay for Mar 15th - Mar 31st (used portion of the first billing period). We need to be able to say "prorate this charge against the first billing period, not against the trial period".

Proraters always act on the order item, so we can't move prorating from RecurringOrderManager into the SubscriptionType.
Instead, our best option is to allow charges to specify a full_billing_period. We can then have $charge->getBillingPeriod() / $charge->getFullBillingPeriod() / $charge->needsProration(). It would also make sense to rename the params on the ProraterInterface. Right now they're $partial_period and $period, instead of $billing_period and $full_billing_period.

CommentFileSizeAuthor
#5 3038991-5.patch27.61 KBbojanz

Comments

bojanz created an issue. See original summary.

bojanz’s picture

Issue summary: View changes
bojanz’s picture

Issue summary: View changes
bojanz’s picture

Title: Prorating logic is not always correct » Prorating logic is not correct for prepaid subscriptions

Moved the tests to monthly intervals in #3039395: Improve the tests, move from hourly to monthly intervals.

The default test billing schedule is postpaid, so the tests still pass. Let's introduce additional coverage here.

bojanz’s picture

Status: Active » Needs review
StatusFileSize
new27.61 KB

  • bojanz committed 9cd0ee2 on 8.x-1.x
    Issue #3038991 by bojanz: Prorating logic is not correct for prepaid...
bojanz’s picture

Status: Needs review » Fixed

Committed.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.