Skip to content

Commit 8898142

Browse files
steveoliverbojanz
steveoliver
authored andcommitted
Issue #2804227 by jackbravo, flocondetoile, zerolab, edwardaa, steveoliver, vasike: Add getTotalPaid() and getBalance() methods to orders
1 parent 03659c6 commit 8898142

File tree

11 files changed

+274
-2
lines changed

11 files changed

+274
-2
lines changed

modules/order/commerce_order.install

+14
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,17 @@ function commerce_order_update_8204() {
7373
$update_manager = \Drupal::entityDefinitionUpdateManager();
7474
$update_manager->installFieldStorageDefinition('uses_legacy_adjustments', 'commerce_order_item', 'commerce_order', $storage_definition);
7575
}
76+
77+
/**
78+
* Add the 'total_paid' field to 'commerce_order' entities.
79+
*/
80+
function commerce_order_update_8205() {
81+
$storage_definition = BaseFieldDefinition::create('commerce_price')
82+
->setLabel(t('Total paid'))
83+
->setDescription(t('The total paid price of the order.'))
84+
->setDisplayConfigurable('form', FALSE)
85+
->setDisplayConfigurable('view', TRUE);
86+
87+
$update_manager = \Drupal::entityDefinitionUpdateManager();
88+
$update_manager->installFieldStorageDefinition('total_paid', 'commerce_order', 'commerce_order', $storage_definition);
89+
}

modules/order/src/Entity/Order.php

+41
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Drupal\commerce\Entity\CommerceContentEntityBase;
66
use Drupal\commerce_order\Adjustment;
7+
use Drupal\commerce_price\Price;
78
use Drupal\commerce_store\Entity\StoreInterface;
89
use Drupal\Core\Entity\EntityChangedTrait;
910
use Drupal\Core\Entity\EntityStorageInterface;
@@ -402,6 +403,40 @@ public function getTotalPrice() {
402403
}
403404
}
404405

406+
/**
407+
* {@inheritdoc}
408+
*/
409+
public function getTotalPaid() {
410+
if (!$this->get('total_paid')->isEmpty()) {
411+
return $this->get('total_paid')->first()->toPrice();
412+
}
413+
elseif ($total_price = $this->getTotalPrice()) {
414+
// Provide a default without storing it, to avoid having to update
415+
// the field if the order currency changes before the order is placed.
416+
return new Price('0', $total_price->getCurrencyCode());
417+
}
418+
}
419+
420+
/**
421+
* {@inheritdoc}
422+
*/
423+
public function setTotalPaid(Price $total_paid) {
424+
$this->set('total_paid', $total_paid);
425+
}
426+
427+
/**
428+
* {@inheritdoc}
429+
*/
430+
public function getBalance() {
431+
if ($total_price = $this->getTotalPrice()) {
432+
$balance = $total_price;
433+
if ($total_paid = $this->getTotalPaid()) {
434+
$balance = $balance->subtract($total_paid);
435+
}
436+
return $balance;
437+
}
438+
}
439+
405440
/**
406441
* {@inheritdoc}
407442
*/
@@ -688,6 +723,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
688723
->setDisplayConfigurable('form', FALSE)
689724
->setDisplayConfigurable('view', TRUE);
690725

726+
$fields['total_paid'] = BaseFieldDefinition::create('commerce_price')
727+
->setLabel(t('Total paid'))
728+
->setDescription(t('The total paid price of the order.'))
729+
->setDisplayConfigurable('form', FALSE)
730+
->setDisplayConfigurable('view', TRUE);
731+
691732
$fields['state'] = BaseFieldDefinition::create('state')
692733
->setLabel(t('State'))
693734
->setDescription(t('The order state.'))

modules/order/src/Entity/OrderInterface.php

+28
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Drupal\commerce_order\Entity;
44

55
use Drupal\commerce_order\EntityAdjustableInterface;
6+
use Drupal\commerce_price\Price;
67
use Drupal\commerce_store\Entity\StoreInterface;
78
use Drupal\Core\Entity\ContentEntityInterface;
89
use Drupal\Core\Entity\EntityChangedInterface;
@@ -275,6 +276,33 @@ public function recalculateTotalPrice();
275276
*/
276277
public function getTotalPrice();
277278

279+
/**
280+
* Gets the total paid price.
281+
*
282+
* @return \Drupal\commerce_price\Price|null
283+
* The total paid price, or NULL.
284+
*/
285+
public function getTotalPaid();
286+
287+
/**
288+
* Sets the total paid price.
289+
*
290+
* @param \Drupal\commerce_price\Price $total_paid
291+
* The total paid price.
292+
*/
293+
public function setTotalPaid(Price $total_paid);
294+
295+
/**
296+
* Gets the order balance.
297+
*
298+
* Calculated by subtracting the total paid price from the total price.
299+
* Can be negative in case the order was overpaid.
300+
*
301+
* @return \Drupal\commerce_price\Price|null
302+
* The order balance, or NULL.
303+
*/
304+
public function getBalance();
305+
278306
/**
279307
* Gets the order state.
280308
*

modules/order/tests/src/Kernel/Entity/OrderTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ protected function setUp() {
9797
* @covers ::getSubtotalPrice
9898
* @covers ::recalculateTotalPrice
9999
* @covers ::getTotalPrice
100+
* @covers ::getTotalPaid
101+
* @covers ::setTotalPaid
102+
* @covers ::getBalance
100103
* @covers ::getState
101104
* @covers ::getRefreshState
102105
* @covers ::setRefreshState
@@ -140,6 +143,7 @@ public function testOrder() {
140143
$order = Order::create([
141144
'type' => 'default',
142145
'state' => 'completed',
146+
'store_id' => $this->store->id(),
143147
]);
144148
$order->save();
145149

@@ -227,6 +231,15 @@ public function testOrder() {
227231
$order->clearAdjustments();
228232
$this->assertEquals($adjustments, $order->getAdjustments());
229233

234+
$this->assertEquals(new Price('0', 'USD'), $order->getTotalPaid());
235+
$this->assertEquals(new Price('17.00', 'USD'), $order->getBalance());
236+
$order->setTotalPaid(new Price('7.00', 'USD'));
237+
$this->assertEquals(new Price('7.00', 'USD'), $order->getTotalPaid());
238+
$this->assertEquals(new Price('10.00', 'USD'), $order->getBalance());
239+
$order->setTotalPaid(new Price('27.00', 'USD'));
240+
$this->assertEquals(new Price('27.00', 'USD'), $order->getTotalPaid());
241+
$this->assertEquals(new Price('-10.00', 'USD'), $order->getBalance());
242+
230243
$this->assertEquals('completed', $order->getState()->value);
231244

232245
$order->setRefreshState(Order::REFRESH_ON_SAVE);

modules/payment/commerce_payment.services.yml

+4
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ services:
2424
commerce_payment.options_builder:
2525
class: Drupal\commerce_payment\PaymentOptionsBuilder
2626
arguments: ['@entity_type.manager', '@string_translation']
27+
28+
commerce_payment.order_manager:
29+
class: Drupal\commerce_payment\PaymentOrderManager
30+
arguments: ['@entity_type.manager']

modules/payment/src/Entity/Payment.php

+28
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,34 @@ public function preSave(EntityStorageInterface $storage) {
311311
}
312312
}
313313

314+
/**
315+
* {@inheritdoc}
316+
*/
317+
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
318+
if ($this->isCompleted()) {
319+
$payment_order_manager = \Drupal::service('commerce_payment.order_manager');
320+
$payment_order_manager->updateTotalPaid($this->getOrder());
321+
}
322+
}
323+
324+
/**
325+
* {@inheritdoc}
326+
*/
327+
public static function postDelete(EntityStorageInterface $storage, array $entities) {
328+
parent::postDelete($storage, $entities);
329+
330+
// Multiple payments might reference the same order, make sure that each
331+
// order is only updated once.
332+
$orders = [];
333+
foreach ($entities as $entity) {
334+
$orders[$entity->getOrderId()] = $entity->getOrder();
335+
}
336+
$payment_order_manager = \Drupal::service('commerce_payment.order_manager');
337+
foreach ($orders as $order) {
338+
$payment_order_manager->updateTotalPaid($order);
339+
}
340+
}
341+
314342
/**
315343
* {@inheritdoc}
316344
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Drupal\commerce_payment;
4+
5+
use Drupal\commerce_order\Entity\OrderInterface;
6+
use Drupal\commerce_price\Price;
7+
use Drupal\Core\Entity\EntityTypeManagerInterface;
8+
9+
class PaymentOrderManager implements PaymentOrderManagerInterface {
10+
11+
/**
12+
* The payment storage.
13+
*
14+
* @var \Drupal\commerce_payment\PaymentStorageInterface
15+
*/
16+
protected $paymentStorage;
17+
18+
/**
19+
* Constructs a new PaymentOrderManager object.
20+
*
21+
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
22+
* The entity type manager.
23+
*/
24+
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
25+
$this->paymentStorage = $entity_type_manager->getStorage('commerce_payment');
26+
}
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function updateTotalPaid(OrderInterface $order) {
32+
$previous_total = $order->getTotalPaid();
33+
if (!$previous_total) {
34+
// A NULL total indicates an order that doesn't have any items yet.
35+
return;
36+
}
37+
// The new total is always calculated from scratch, to properly handle
38+
// orders that were created before the total_paid field was introduced.
39+
$payments = $this->paymentStorage->loadMultipleByOrder($order);
40+
/** @var \Drupal\commerce_price\Price $new_total */
41+
$new_total = new Price('0', $previous_total->getCurrencyCode());
42+
foreach ($payments as $payment) {
43+
if ($payment->isCompleted()) {
44+
$new_total = $new_total->add($payment->getBalance());
45+
}
46+
}
47+
48+
if (!$previous_total->equals($new_total)) {
49+
$order->setTotalPaid($new_total);
50+
$order->save();
51+
}
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Drupal\commerce_payment;
4+
5+
use Drupal\commerce_order\Entity\OrderInterface;
6+
7+
/**
8+
* Updates orders based on payment information.
9+
*/
10+
interface PaymentOrderManagerInterface {
11+
12+
/**
13+
* Recalculates the total paid price for the given order.
14+
*
15+
* The order will be saved if the total paid price has changed.
16+
*
17+
* @param \Drupal\commerce_order\Entity\OrderInterface $order
18+
* The order.
19+
*/
20+
public function updateTotalPaid(OrderInterface $order);
21+
22+
}

modules/payment/tests/src/Functional/DefaultPaymentAdminTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ public function testPaymentCreation() {
153153
$this->assertSession()->addressEquals($this->paymentUri);
154154
$this->assertSession()->pageTextContains('Completed');
155155

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

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

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

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

265+
\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
261266
$payment = Payment::load($payment->id());
262267
$this->assertNull($payment);
263268
}

modules/payment/tests/src/Functional/ManualPaymentAdminTest.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public function testPaymentCreation() {
110110
$this->submitForm(['payment[amount][number]' => '100'], 'Add payment');
111111
$this->assertSession()->addressEquals($this->paymentUri);
112112
$this->assertSession()->pageTextContains('Pending');
113+
114+
\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([1]);
113115
/** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
114116
$payment = Payment::load(1);
115117
$this->assertEquals($payment->getOrderId(), $this->order->id());
@@ -122,6 +124,8 @@ public function testPaymentCreation() {
122124
$this->submitForm(['payment[amount][number]' => '100', 'payment[received]' => TRUE], 'Add payment');
123125
$this->assertSession()->addressEquals($this->paymentUri);
124126
$this->assertSession()->pageTextContains('Completed');
127+
128+
\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([2]);
125129
/** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
126130
$payment = Payment::load(2);
127131
$this->assertEquals($payment->getOrderId(), $this->order->id());
@@ -147,6 +151,7 @@ public function testPaymentReceive() {
147151
$this->assertSession()->pageTextNotContains('Pending');
148152
$this->assertSession()->pageTextContains('Completed');
149153

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

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

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

197+
\Drupal::entityTypeManager()->getStorage('commerce_payment')->resetCache([$payment->id()]);
191198
$payment = Payment::load($payment->id());
192199
$this->assertEquals($payment->getState()->getLabel(), 'Voided');
193200
}

0 commit comments

Comments
 (0)