Problem/Motivation

Like #2790545: Add $payment->getBalance(), Order entities needs a getBalance() method. The problem is we don't want commerce_order to depend on commerce_payment but order->getBalance() will need access to any payments made.

Proposed Resolution

Find a way for orders to be able to discover any payments made for an order without requiring a hard dependency on the commerce_payment module.

Remaining Tasks

Discuss: How do we accomplish this? Balance will be order total unless we have a payment service? Then we try to see if there are any payments for the order? Or commerce_order subscribes to commerce_payment events?

Follow PR on GitHub: https://github.com/drupalcommerce/commerce/pull/508

CommentFileSizeAuthor
#33 commerce-add-order-balance-2804227-32.patch14.56 KBzerolab
#31 commerce-add-order-balance-2804227-31-no-double-payment.patch15.87 KBzerolab
#31 commerce-add-order-balance-2804227-31.patch14.5 KBzerolab
#27 interdiff-2804227-25-27.txt3.74 KBedaa
#27 2804227-27.patch13.79 KBedaa
#25 2804227-23-25-interdiff.txt753 bytesflocondetoile
#25 2804227-25.patch12.86 KBflocondetoile
#23 2804227-21-23-interdiff.txt818 bytesflocondetoile
#23 2804227-23.patch12.81 KBflocondetoile
#21 2804227-19-21.diff624 bytesflocondetoile
#21 2804227-21.patch12.75 KBflocondetoile
#19 2804227-19.patch12.75 KBflocondetoile
#18 update_for_total_paid_field-2804227-18.patch32.31 KBjackbravo
#18 update_for_total_paid_field-2804227-18.diff14.19 KBjackbravo
#16 update_for_total_paid_field-2804227-16.patch31.44 KBjackbravo
#16 update_for_total_paid_field-2804227-16.diff13.64 KBjackbravo
#15 update_for_total_paid_field-2804227-14.diff12.65 KBjackbravo
#14 update_for_total_paid_field-2804227-14.patch27 KBjackbravo
#12 update_for_total_paid_field-2804227-12.patch1.07 KBvasike
#9 2804227-9--add-order-balance.patch4.51 KBsteveoliver
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

steveoliver created an issue. See original summary.

mglaman’s picture

The best solution, I can think of, is to pass the Order to a Balance service in the Commerce Payment module. This returns the balance of the order.

bojanz’s picture

I was thinking of a $order->paid_total that gets updated from commerce_payment when a payment gets captured or refunded.

agoradesign’s picture

I like bojanz' idea more. So you'd always have the field and the getBalance() function, no matter if you enable the Payment module or not. Sounds more right

mglaman’s picture

So we'd acknowledge that orders have a "balance", via a method, but no property? Or would order provide the property, too. Or would it be an untyped method and just a base field payment adds?

bojanz’s picture

The order tracks its paid_total. getBalance() then subtracts the paid_total from the total.
I think it makes sense for an order to have this concept regardless of commerce_payment.

agoradesign’s picture

Exactly.. Doing this will allow us to ask the order directly for its balance, not a service instead. + you could theoretically implement a very custom payment extension, that doesn't rely on commerce_payment at all, but still have the paid_total field and the getBalance() function.

And last but not least it makes conceptually sense. The order always knows, whether and how much was paid, no matter what module actually does payments

steveoliver’s picture

Assigned: Unassigned » steveoliver

Sounds good. I'll work on this today.

steveoliver’s picture

Status: Active » Needs review
FileSize
4.51 KB

First pass.

steveoliver’s picture

Issue summary: View changes

Add github PR link.

steveoliver’s picture

Added passing tests for the order::getBalance and related methods: https://github.com/drupalcommerce/commerce/pull/508/files#diff-a708b62f9....

vasike’s picture

Status: Needs review » Needs work
FileSize
1.07 KB

i think we need an update to add the "total_paid" field.

here is patch that could be added to the PR

jackbravo’s picture

Status: Needs work » Needs review

This is a reroll of previous patch, adding also the commerce_order_post_update_6() function proposed in #12. There is already a PR in github with number [508](https://github.com/drupalcommerce/commerce/pull/508), but here is the udpated one: https://github.com/drupalcommerce/commerce/pull/786

IMO, If the tests pass I think the patch should be good to go and needing only another developer review to mark as RTBC.

jackbravo’s picture

This is the latest patch. I believe tests are passing now.

jackbravo’s picture

I realized that asking for the PR.patch gives you this nice format that's more like an interdiff with every commit neatly separated. And if you ask for github's PR.diff, then you get what we generally expect to post here as a patch, which is just one big patch which shows the differences between current dev branch and the change we want. So I'm posting that here also.

jackbravo’s picture

I added one more change. Now when we calculate the balance, we consider the payment status. The tests are also updated for this. So I think this is now RTBC, but will wait for someone else to review it.

Here is the diff and the interdiff (the .patch file is like an interdiff right?).

steveoliver’s picture

@jackbravo-

The patch looks great. Good call on the check of payment state! Super minor nitpick: In OrderInterface.php: "Gets the remaining amount unpaid on the order." I'd remove the word "remaining" -- it seems redundant.

If I didn't work on this, I'd RTBC it, but +1 for RTBC!

jackbravo’s picture

Sounds good. This is a new patch, changing the getBalance() description. Also a re-roll because there where some conflicts in modules/order/commerce_order.post_update.php.

The PR is also up to date.

flocondetoile’s picture

Patch #18 doesn't apply well on commerce 2.2, especially for the order tests.
Rerolled Patch #18 on commerce 2.2 and updated the tests.

Status: Needs review » Needs work

The last submitted patch, 19: 2804227-19.patch, failed testing. View results

flocondetoile’s picture

Status: Needs work » Needs review
FileSize
12.75 KB
624 bytes

Oups

Status: Needs review » Needs work

The last submitted patch, 21: 2804227-19-21.diff, failed testing. View results

flocondetoile’s picture

Status: Needs work » Needs review
FileSize
12.81 KB
818 bytes

Should pass.

Status: Needs review » Needs work

The last submitted patch, 23: 2804227-23.patch, failed testing. View results

flocondetoile’s picture

Status: Needs work » Needs review
FileSize
12.86 KB
753 bytes

Again...

edaa’s picture

Status: Needs review » Needs work
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageInterface $storage, array $entities) {
+    parent::preDelete($storage, $entities);
+
+    // Subtract each payment from order.
+    foreach ($entities as $payment) {
+      $net_payment = $payment->getAmount()->subtract($payment->getRefundedAmount());
+      $payment->getOrder()->subtractPayment($net_payment)->save();
+    }

Need to check payment state before subtracting:

  // Subtract each payment from order.
  foreach ($entities as $payment) {
    if (in_array($payment->getState()->value, ['completed', 'partially_refunded'])) {
      $payment->getOrder()->subtractPayment($payment->getBalance())->save();
    }
  }
edaa’s picture

Status: Needs work » Needs review
FileSize
13.79 KB
3.74 KB

As per comment #26

bojanz’s picture

Jaesin’s picture

+1 for this. I'm working around it for the time being.

bojanz’s picture

The main question here is what to do with existing orders:

1) Initialize paid_total to 0.
2) Initialize paid_total to the total_price (treat all previous orders as fully paid).
3) Have an update hook that resaves all orders (potentially 10s of thousands).

zerolab’s picture

Attaching a re-rolled patch with a couple of minor tweaks.

May need to update the tests further.

Note that this does not tackle the questions in #30 yet.

Best,
Dan

editmoved the patch for duplicate payment to #3003832: PaymentProcess needs to take the order balance into account

Status: Needs review » Needs work

The last submitted patch, 31: commerce-add-order-balance-2804227-31-no-double-payment.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

zerolab’s picture

Status: Needs work » Needs review
FileSize
14.56 KB

Missed a line. The patch combines #27 with updated tests from https://github.com/drupalcommerce/commerce/pull/855
Providing an interdiff is rather fiddly.

bojanz’s picture

Assigned: steveoliver » bojanz

I'm wrapping up the final iteration of this patch.

Here are the problems I noticed in #33:

+/**
+ * Add 'total_paid' field to 'commerce_order' entities.
+ */
+function commerce_order_post_update_add_field_total_paid() {

New fields should be added in update hooks, not post update hooks.

+  /**
+   * {@inheritdoc}
+   */
+  public function addPayment(Price $amount) {
+    $this->setTotalPaid($this->getTotalPaid()->add($amount));
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function subtractPayment(Price $amount) {
+    $this->setTotalPaid($this->getTotalPaid()->subtract($amount));
+    return $this;
+  }

It is odd to have addPayment/setPayment methods that do not accept a payment.
Since this calculation logic is insufficient anyway (more on that down below), we should remove these methods and just rely on the regular setter.

+  public function getTotalPaid() {
+    if (!$this->get('total_paid')->isEmpty()) {
+      return $this->get('total_paid')->first()->toPrice();
+    }
+    return new Price('0', $this->getStore()->getDefaultCurrencyCode());
+  }

$this->getStore()->getDefaultCurrencyCode() is probably from a time when we did the same in other order methods, but it proved to be buggy in multicurrency scenarios. If the field is empty, we should be returning NULL, no fallback.

+  /**
+   * Gets the amount unpaid on the order.
+   *
+   * @return \Drupal\commerce_price\Price|null
+   *   The total order amount minus the total paid, or NULL.
+   */

We need to rewrite this docblock to say how the order balance is calculated, and that it can be negative if the order was overpaid.

In general, we need a service that will recalculate an order's total paid price. That will allow people to update all existing orders if desired. We won't do it ourselves, cause we assume that sites have potentially too many orders to update from update.php, making it better to do it from a Drush/Console command. It will also allow us to heal existing orders that are getting payment updates (if an order with a NULL total_paid gets a refund, the total_paid field can't become negative, we need to recalculate from scratch).

bojanz’s picture

Updating issue credits. Final test run on Travis.

bojanz’s picture

Title: Add $order->getBalance() » Add getTotalPaid() and getBalance() methods to orders

  • bojanz committed 8898142 on 8.x-2.x authored by steveoliver
    Issue #2804227 by jackbravo, flocondetoile, zerolab, edwardaa,...
bojanz’s picture

Status: Needs review » Fixed

Thanks everyone! This took a while :)

Status: Fixed » Closed (fixed)

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