Skip to content

Commit

Permalink
Issue #2804227 by jackbravo, flocondetoile, zerolab, edwardaa, steveo…
Browse files Browse the repository at this point in the history
…liver, vasike: Add $order->getBalance()
  • Loading branch information
steveoliver authored and bojanz committed Oct 8, 2018
1 parent 03659c6 commit 807c6e8
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 2 deletions.
14 changes: 14 additions & 0 deletions modules/order/commerce_order.install
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,17 @@ function commerce_order_update_8204() {
$update_manager = \Drupal::entityDefinitionUpdateManager();
$update_manager->installFieldStorageDefinition('uses_legacy_adjustments', 'commerce_order_item', 'commerce_order', $storage_definition);
}

/**
* Add the 'total_paid' field to 'commerce_order' entities.
*/
function commerce_order_update_8205() {
$storage_definition = BaseFieldDefinition::create('commerce_price')
->setLabel(t('Total paid'))
->setDescription(t('The total paid price of the order.'))
->setDisplayConfigurable('form', FALSE)
->setDisplayConfigurable('view', TRUE);

$update_manager = \Drupal::entityDefinitionUpdateManager();
$update_manager->installFieldStorageDefinition('total_paid', 'commerce_order', 'commerce_order', $storage_definition);
}
41 changes: 41 additions & 0 deletions modules/order/src/Entity/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Drupal\commerce\Entity\CommerceContentEntityBase;
use Drupal\commerce_order\Adjustment;
use Drupal\commerce_price\Price;
use Drupal\commerce_store\Entity\StoreInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityStorageInterface;
Expand Down Expand Up @@ -402,6 +403,40 @@ public function getTotalPrice() {
}
}

/**
* {@inheritdoc}
*/
public function getTotalPaid() {
if (!$this->get('total_paid')->isEmpty()) {
return $this->get('total_paid')->first()->toPrice();
}
elseif ($total_price = $this->getTotalPrice()) {
// Provide a default without storing it, to avoid having to update
// the field if the order currency changes before the order is placed.
return new Price('0', $total_price->getCurrencyCode());
}
}

/**
* {@inheritdoc}
*/
public function setTotalPaid(Price $total_paid) {
$this->set('total_paid', $total_paid);
}

/**
* {@inheritdoc}
*/
public function getBalance() {
if ($total_price = $this->getTotalPrice()) {
$balance = $total_price;
if ($total_paid = $this->getTotalPaid()) {
$balance = $balance->subtract($total_paid);
}
return $balance;
}
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -688,6 +723,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDisplayConfigurable('form', FALSE)
->setDisplayConfigurable('view', TRUE);

$fields['total_paid'] = BaseFieldDefinition::create('commerce_price')
->setLabel(t('Total paid'))
->setDescription(t('The total paid price of the order.'))
->setDisplayConfigurable('form', FALSE)
->setDisplayConfigurable('view', TRUE);

$fields['state'] = BaseFieldDefinition::create('state')
->setLabel(t('State'))
->setDescription(t('The order state.'))
Expand Down
28 changes: 28 additions & 0 deletions modules/order/src/Entity/OrderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Drupal\commerce_order\Entity;

use Drupal\commerce_order\EntityAdjustableInterface;
use Drupal\commerce_price\Price;
use Drupal\commerce_store\Entity\StoreInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
Expand Down Expand Up @@ -275,6 +276,33 @@ public function recalculateTotalPrice();
*/
public function getTotalPrice();

/**
* Gets the total paid price.
*
* @return \Drupal\commerce_price\Price|null
* The total paid price, or NULL.
*/
public function getTotalPaid();

/**
* Sets the total paid price.
*
* @param \Drupal\commerce_price\Price $total_paid
* The total paid price.
*/
public function setTotalPaid(Price $total_paid);

/**
* Gets the order balance.
*
* Calculated by subtracting the total paid price from the total price.
* Can be negative in case the order was overpaid.
*
* @return \Drupal\commerce_price\Price|null
* The order balance, or NULL.
*/
public function getBalance();

/**
* Gets the order state.
*
Expand Down
13 changes: 13 additions & 0 deletions modules/order/tests/src/Kernel/Entity/OrderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ protected function setUp() {
* @covers ::getSubtotalPrice
* @covers ::recalculateTotalPrice
* @covers ::getTotalPrice
* @covers ::getTotalPaid
* @covers ::setTotalPaid
* @covers ::getBalance
* @covers ::getState
* @covers ::getRefreshState
* @covers ::setRefreshState
Expand Down Expand Up @@ -140,6 +143,7 @@ public function testOrder() {
$order = Order::create([
'type' => 'default',
'state' => 'completed',
'store_id' => $this->store->id(),
]);
$order->save();

Expand Down Expand Up @@ -227,6 +231,15 @@ public function testOrder() {
$order->clearAdjustments();
$this->assertEquals($adjustments, $order->getAdjustments());

$this->assertEquals(new Price('0', 'USD'), $order->getTotalPaid());
$this->assertEquals(new Price('17.00', 'USD'), $order->getBalance());
$order->setTotalPaid(new Price('7.00', 'USD'));
$this->assertEquals(new Price('7.00', 'USD'), $order->getTotalPaid());
$this->assertEquals(new Price('10.00', 'USD'), $order->getBalance());
$order->setTotalPaid(new Price('27.00', 'USD'));
$this->assertEquals(new Price('27.00', 'USD'), $order->getTotalPaid());
$this->assertEquals(new Price('-10.00', 'USD'), $order->getBalance());

$this->assertEquals('completed', $order->getState()->value);

$order->setRefreshState(Order::REFRESH_ON_SAVE);
Expand Down
4 changes: 4 additions & 0 deletions modules/payment/commerce_payment.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ services:
commerce_payment.options_builder:
class: Drupal\commerce_payment\PaymentOptionsBuilder
arguments: ['@entity_type.manager', '@string_translation']

commerce_payment.order_manager:
class: Drupal\commerce_payment\PaymentOrderManager
arguments: ['@entity_type.manager']
28 changes: 28 additions & 0 deletions modules/payment/src/Entity/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,34 @@ public function preSave(EntityStorageInterface $storage) {
}
}

/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
if ($this->isCompleted()) {
$payment_order_manager = \Drupal::service('commerce_payment.order_manager');
$payment_order_manager->updateTotalPaid($this->getOrder());
}
}

/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);

// Multiple payments might reference the same order, make sure that each
// order is only updated once.
$orders = [];
foreach ($entities as $entity) {
$orders[$entity->getOrderId()] = $entity->getOrder();
}
$payment_order_manager = \Drupal::service('commerce_payment.order_manager');
foreach ($orders as $order) {
$payment_order_manager->updateTotalPaid($order);
}
}

/**
* {@inheritdoc}
*/
Expand Down
54 changes: 54 additions & 0 deletions modules/payment/src/PaymentOrderManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Drupal\commerce_payment;

use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_price\Price;
use Drupal\Core\Entity\EntityTypeManagerInterface;

class PaymentOrderManager implements PaymentOrderManagerInterface {

/**
* The payment storage.
*
* @var \Drupal\commerce_payment\PaymentStorageInterface
*/
protected $paymentStorage;

/**
* Constructs a new PaymentOrderManager object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->paymentStorage = $entity_type_manager->getStorage('commerce_payment');
}

/**
* {@inheritdoc}
*/
public function updateTotalPaid(OrderInterface $order) {
$previous_total = $order->getTotalPaid();
if (!$previous_total) {
// A NULL total indicates an order that doesn't have any items yet.
return;
}
// The new total is always calculated from scratch, to properly handle
// orders that were created before the total_paid field was introduced.
$payments = $this->paymentStorage->loadMultipleByOrder($order);
/** @var \Drupal\commerce_price\Price $new_total */
$new_total = new Price('0', $previous_total->getCurrencyCode());
foreach ($payments as $payment) {
if ($payment->isCompleted()) {
$new_total = $new_total->add($payment->getBalance());
}
}

if (!$previous_total->equals($new_total)) {
$order->setTotalPaid($new_total);
$order->save();
}
}

}
22 changes: 22 additions & 0 deletions modules/payment/src/PaymentOrderManagerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Drupal\commerce_payment;

use Drupal\commerce_order\Entity\OrderInterface;

/**
* Updates orders based on payment information.
*/
interface PaymentOrderManagerInterface {

/**
* Recalculates the total paid price for the given order.
*
* The order will be saved if the total paid price has changed.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The order.
*/
public function updateTotalPaid(OrderInterface $order);

}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public function testPaymentCreation() {
$this->assertSession()->addressEquals($this->paymentUri);
$this->assertSession()->pageTextContains('Completed');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([1]);
/** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
$payment = Payment::load(1);
$this->assertEquals($payment->getOrderId(), $this->order->id());
Expand Down Expand Up @@ -182,6 +183,7 @@ public function testPaymentCapture() {
$this->assertSession()->pageTextNotContains('Authorization');
$this->assertSession()->pageTextContains('Completed');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertEquals($payment->getState()->getLabel(), 'Completed');
}
Expand All @@ -208,6 +210,7 @@ public function testPaymentRefund() {
$this->assertSession()->pageTextNotContains('Completed');
$this->assertSession()->pageTextContains('Refunded');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertEquals($payment->getState()->getLabel(), 'Refunded');
}
Expand All @@ -233,6 +236,7 @@ public function testPaymentVoid() {
$this->assertSession()->addressEquals($this->paymentUri);
$this->assertSession()->pageTextContains('Authorization (Voided)');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertEquals($payment->getState()->getLabel(), 'Authorization (Voided)');
}
Expand All @@ -258,6 +262,7 @@ public function testPaymentDelete() {
$this->assertSession()->addressEquals($this->paymentUri);
$this->assertSession()->pageTextNotContains('Authorization');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertNull($payment);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public function testPaymentCreation() {
$this->submitForm(['payment[amount][number]' => '100'], 'Add payment');
$this->assertSession()->addressEquals($this->paymentUri);
$this->assertSession()->pageTextContains('Pending');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([1]);
/** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
$payment = Payment::load(1);
$this->assertEquals($payment->getOrderId(), $this->order->id());
Expand All @@ -122,6 +124,8 @@ public function testPaymentCreation() {
$this->submitForm(['payment[amount][number]' => '100', 'payment[received]' => TRUE], 'Add payment');
$this->assertSession()->addressEquals($this->paymentUri);
$this->assertSession()->pageTextContains('Completed');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([2]);
/** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
$payment = Payment::load(2);
$this->assertEquals($payment->getOrderId(), $this->order->id());
Expand All @@ -147,6 +151,7 @@ public function testPaymentReceive() {
$this->assertSession()->pageTextNotContains('Pending');
$this->assertSession()->pageTextContains('Completed');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertEquals($payment->getState()->getLabel(), 'Completed');
}
Expand All @@ -168,8 +173,9 @@ public function testPaymentRefund() {
$this->assertSession()->pageTextNotContains('Completed');
$this->assertSession()->pageTextContains('Refunded');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertEquals($payment->getState()->getLabel(), 'Refunded');
$this->assertEquals('Refunded', $payment->getState()->getLabel());
}

/**
Expand All @@ -188,6 +194,7 @@ public function testPaymentVoid() {
$this->assertSession()->addressEquals($this->paymentUri);
$this->assertSession()->pageTextContains('Voided');

\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
$payment = Payment::load($payment->id());
$this->assertEquals($payment->getState()->getLabel(), 'Voided');
}
Expand Down
Loading

0 comments on commit 807c6e8

Please sign in to comment.