From 350d1846a04980e57d140a188c08bdeacbc21187 Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Fri, 23 Sep 2016 13:35:53 -0700 Subject: [PATCH 01/18] Issue #2804227 Add total_paid field and Order::getBalance() and related methods. --- modules/order/src/Entity/Order.php | 50 +++++++++++++++++++++ modules/order/src/Entity/OrderInterface.php | 44 ++++++++++++++++++ modules/payment/src/Entity/Payment.php | 2 + 3 files changed, 96 insertions(+) diff --git a/modules/order/src/Entity/Order.php b/modules/order/src/Entity/Order.php index a9a1239fb4..573c9a0957 100644 --- a/modules/order/src/Entity/Order.php +++ b/modules/order/src/Entity/Order.php @@ -364,6 +364,49 @@ public function getTotalPrice() { } } + /** + * {@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; + } + + /** + * {@inheritdoc} + */ + public function getTotalPaid() { + if (!$this->get('total_paid')->isEmpty()) { + return $this->get('total_paid')->first()->toPrice(); + } + return new Price('0', $this->getStore()->getDefaultCurrencyCode()); + } + + /** + * {@inheritdoc} + */ + public function setTotalPaid(Price $amount) { + $this->set('total_paid', $amount); + } + + /** + * {@inheritdoc} + */ + public function getBalance() { + if ($this->getTotalPrice() && $this->getTotalPaid()) { + return $this->getTotalPrice()->subtract($this->getTotalPaid()); + } + return $this->getTotalPrice(); + } + /** * {@inheritdoc} */ @@ -623,6 +666,13 @@ 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 amount paid on the order.')) + ->setReadOnly(TRUE) + ->setDisplayConfigurable('form', FALSE) + ->setDisplayConfigurable('view', TRUE); + $fields['state'] = BaseFieldDefinition::create('state') ->setLabel(t('State')) ->setDescription(t('The order state.')) diff --git a/modules/order/src/Entity/OrderInterface.php b/modules/order/src/Entity/OrderInterface.php index 3fbf0ce7d8..fa54c866d8 100644 --- a/modules/order/src/Entity/OrderInterface.php +++ b/modules/order/src/Entity/OrderInterface.php @@ -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; @@ -262,6 +263,49 @@ public function recalculateTotalPrice(); */ public function getTotalPrice(); + /** + * Adds an amount to the order total paid. + * + * @param \Drupal\commerce_price\Price $amount + * The amount to add to the total paid. + * + * @return $this + */ + public function addPayment(Price $amount); + + /** + * Subtracts an amount from the order total paid. + * + * @param \Drupal\commerce_price\Price $amount + * The amount to subtract from the total paid. + * + * @return $this + */ + public function subtractPayment(Price $amount); + + /** + * Gets the total amount paid on the order. + * + * @return \Drupal\commerce_price\Price + * The order total paid amount. + */ + public function getTotalPaid(); + + /** + * Sets the total amount paid on the order. + * + * @param \Drupal\commerce_price\Price $amount + */ + public function setTotalPaid(Price $amount); + + /** + * Gets the remaining amount unpaid on the order. + * + * @return \Drupal\commerce_price\Price|null + * The total order amount minus the total paid, or NULL. + */ + public function getBalance(); + /** * Gets the order state. * diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php index 91a5e659d7..dcf9e456d2 100644 --- a/modules/payment/src/Entity/Payment.php +++ b/modules/payment/src/Entity/Payment.php @@ -175,6 +175,7 @@ public function getAmount() { */ public function setAmount(Price $amount) { $this->set('amount', $amount); + $this->getOrder()->addPayment($amount); return $this; } @@ -192,6 +193,7 @@ public function getRefundedAmount() { */ public function setRefundedAmount(Price $refunded_amount) { $this->set('refunded_amount', $refunded_amount); + $this->getOrder()->subtractPayment($refunded_amount); return $this; } From 87508a3a31f8fc4d7fb1790d86b2adfe862635b4 Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Fri, 23 Sep 2016 16:37:41 -0700 Subject: [PATCH 02/18] Fix OrderInterface docblocks (thanks, phpcs). --- modules/order/src/Entity/OrderInterface.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/order/src/Entity/OrderInterface.php b/modules/order/src/Entity/OrderInterface.php index fa54c866d8..2519ca8114 100644 --- a/modules/order/src/Entity/OrderInterface.php +++ b/modules/order/src/Entity/OrderInterface.php @@ -267,7 +267,7 @@ public function getTotalPrice(); * Adds an amount to the order total paid. * * @param \Drupal\commerce_price\Price $amount - * The amount to add to the total paid. + * The amount to add to the total paid. * * @return $this */ @@ -277,7 +277,7 @@ public function addPayment(Price $amount); * Subtracts an amount from the order total paid. * * @param \Drupal\commerce_price\Price $amount - * The amount to subtract from the total paid. + * The amount to subtract from the total paid. * * @return $this */ @@ -295,6 +295,7 @@ public function getTotalPaid(); * Sets the total amount paid on the order. * * @param \Drupal\commerce_price\Price $amount + * The amount to set as the order total paid. */ public function setTotalPaid(Price $amount); From c31e6ce64aeb1fe9e9abed27363c518d4d6f81ed Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Mon, 17 Oct 2016 13:23:55 -0700 Subject: [PATCH 03/18] Add Order tests for ::getBalance, ::getTotalPaid, ::setTotalPaid, ::addPayment, ::subtractPayment. --- .../order/tests/src/Kernel/Entity/OrderTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index ebdf2bdcb6..efb205512e 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -94,6 +94,11 @@ protected function setUp() { * @covers ::getSubtotalPrice * @covers ::recalculateTotalPrice * @covers ::getTotalPrice + * @covers ::getBalance + * @covers ::addPayment + * @covers ::subtractPayment + * @covers ::setTotalPaid + * @covers ::getTotalPaid * @covers ::getState * @covers ::getRefreshState * @covers ::setRefreshState @@ -178,6 +183,7 @@ public function testOrder() { $this->assertNotEmpty($order->hasItem($another_order_item)); $this->assertEquals(new Price('8.00', 'USD'), $order->getTotalPrice()); + $this->assertEquals(new Price('8.00', 'USD'), $order->getBalance()); $adjustments = []; $adjustments[] = new Adjustment([ 'type' => 'custom', @@ -199,6 +205,7 @@ public function testOrder() { $order->removeAdjustment($adjustments[0]); $this->assertEquals(new Price('8.00', 'USD'), $order->getSubtotalPrice()); $this->assertEquals(new Price('18.00', 'USD'), $order->getTotalPrice()); + $this->assertEquals(new Price('18.00', 'USD'), $order->getBalance()); $this->assertEquals([$adjustments[1]], $order->getAdjustments()); $order->setAdjustments($adjustments); $this->assertEquals($adjustments, $order->getAdjustments()); @@ -215,6 +222,14 @@ public function testOrder() { $this->assertEquals(new Price('27.00', 'USD'), $order->getTotalPrice()); $collected_adjustments = $order->collectAdjustments(); $this->assertEquals(new Price('10.00', 'USD'), $collected_adjustments[2]->getAmount()); + $order->addPayment(new Price('25.00', 'USD')); + $this->assertEquals(new Price('25.00', 'USD'), $order->getTotalPaid()); + $this->assertEquals(new Price('2.00', 'USD'), $order->getBalance()); + $order->subtractPayment(new Price('5.00', 'USD')); + $this->assertEquals(new Price('20.00', 'USD'), $order->getTotalPaid()); + $this->assertEquals(new Price('7.00', 'USD'), $order->getBalance()); + $order->setTotalPaid(new Price('27.00', 'USD')); + $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); $this->assertEquals('completed', $order->getState()->value); From fa1b88d6799253337c362347b68e39c15bdda654 Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Tue, 7 Feb 2017 14:20:56 -0800 Subject: [PATCH 04/18] Add and subtract payments to and from order when saving and deleting payment entities. --- .../tests/src/Kernel/Entity/OrderTest.php | 42 +++++++++++++++++-- modules/payment/src/Entity/Payment.php | 23 +++++++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index efb205512e..927617edf4 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -8,6 +8,8 @@ use Drupal\commerce_order\Entity\OrderItemType; use Drupal\commerce_price\Exception\CurrencyMismatchException; use Drupal\commerce_price\Price; +use Drupal\commerce_payment\Entity\Payment; +use Drupal\commerce_payment\Entity\PaymentGateway; use Drupal\profile\Entity\Profile; use Drupal\Tests\commerce\Kernel\CommerceKernelTestBase; @@ -36,6 +38,8 @@ class OrderTest extends CommerceKernelTestBase { 'entity_reference_revisions', 'profile', 'state_machine', + 'commerce_payment', + 'commerce_payment_example', 'commerce_product', 'commerce_order', ]; @@ -49,6 +53,7 @@ protected function setUp() { $this->installEntitySchema('profile'); $this->installEntitySchema('commerce_order'); $this->installEntitySchema('commerce_order_item'); + $this->installEntitySchema('commerce_payment'); $this->installConfig('commerce_order'); // An order item type that doesn't need a purchasable entity, for simplicity. @@ -58,6 +63,12 @@ protected function setUp() { 'orderType' => 'default', ])->save(); + PaymentGateway::create([ + 'id' => 'example', + 'label' => 'Example', + 'plugin' => 'example_onsite', + ])->save(); + $user = $this->createUser(); $this->user = $this->reloadEntity($user); } @@ -138,6 +149,7 @@ public function testOrder() { $order = Order::create([ 'type' => 'default', 'state' => 'completed', + 'store_id' => $this->store->id(), ]); $order->save(); @@ -220,16 +232,40 @@ public function testOrder() { ])); $order->addItem($another_order_item); $this->assertEquals(new Price('27.00', 'USD'), $order->getTotalPrice()); + $this->assertEquals(new Price('0', 'USD'), $order->getTotalPaid()); $collected_adjustments = $order->collectAdjustments(); $this->assertEquals(new Price('10.00', 'USD'), $collected_adjustments[2]->getAmount()); - $order->addPayment(new Price('25.00', 'USD')); + + // Test that payments update the order total paid and balance. + $order->save(); + $payment = Payment::create([ + 'order_id' => $order->id(), + 'amount' => new Price('25.00', 'USD'), + 'payment_gateway' => 'example', + ]); + $payment->save(); + $order = Order::load($order->id()); $this->assertEquals(new Price('25.00', 'USD'), $order->getTotalPaid()); $this->assertEquals(new Price('2.00', 'USD'), $order->getBalance()); - $order->subtractPayment(new Price('5.00', 'USD')); + $payment->setRefundedAmount(new Price('5.00', 'USD'))->save(); + $order = Order::load($order->id()); $this->assertEquals(new Price('20.00', 'USD'), $order->getTotalPaid()); $this->assertEquals(new Price('7.00', 'USD'), $order->getBalance()); - $order->setTotalPaid(new Price('27.00', 'USD')); + $payment->delete(); + $order = Order::load($order->id()); + $this->assertEquals(new Price('0.00', 'USD'), $order->getTotalPaid()); + $payment2 = Payment::create([ + 'order_id' => $order->id(), + 'amount' => new Price('27.00', 'USD'), + 'payment_gateway' => 'example', + ]); + $payment2->save(); + $order = Order::load($order->id()); $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); + // Test that the total paid amount can be set explicitly on the order. + $order->setTotalPaid(new Price('0.00', 'USD')); + $order->save(); + $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); $this->assertEquals('completed', $order->getState()->value); diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php index dcf9e456d2..873c04eaf7 100644 --- a/modules/payment/src/Entity/Payment.php +++ b/modules/payment/src/Entity/Payment.php @@ -175,7 +175,6 @@ public function getAmount() { */ public function setAmount(Price $amount) { $this->set('amount', $amount); - $this->getOrder()->addPayment($amount); return $this; } @@ -193,7 +192,6 @@ public function getRefundedAmount() { */ public function setRefundedAmount(Price $refunded_amount) { $this->set('refunded_amount', $refunded_amount); - $this->getOrder()->subtractPayment($refunded_amount); return $this; } @@ -276,6 +274,27 @@ public function preSave(EntityStorageInterface $storage) { $refunded_amount = new Price('0', $this->getAmount()->getCurrencyCode()); $this->setRefundedAmount($refunded_amount); } + + // Add or subtract payments from order. + if ($this->isNew()) { + $this->getOrder()->addPayment($this->getAmount())->save(); + } + else { + $this->getOrder()->subtractPayment($this->getRefundedAmount())->save(); + } + } + + /** + * {@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(); + } } /** From 11b2b7fd5f34324ed2d5c72025894367cf31fd05 Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Tue, 7 Feb 2017 16:31:47 -0800 Subject: [PATCH 05/18] Support multiple partial refunds. --- .../tests/src/Kernel/Entity/OrderTest.php | 59 +++++++++++++++++-- modules/payment/src/Entity/Payment.php | 4 +- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index 927617edf4..a746a5e955 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -29,6 +29,11 @@ class OrderTest extends CommerceKernelTestBase { */ protected $user; + /** + * @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface; + */ + protected $payment_gateway; + /** * Modules to enable. * @@ -63,11 +68,13 @@ protected function setUp() { 'orderType' => 'default', ])->save(); - PaymentGateway::create([ + $payment_gateway = PaymentGateway::create([ 'id' => 'example', 'label' => 'Example', 'plugin' => 'example_onsite', - ])->save(); + ]); + $payment_gateway->save(); + $this->payment_gateway = $payment_gateway; $user = $this->createUser(); $this->user = $this->reloadEntity($user); @@ -231,10 +238,54 @@ public function testOrder() { 'amount' => new Price('5.00', 'USD'), ])); $order->addItem($another_order_item); - $this->assertEquals(new Price('27.00', 'USD'), $order->getTotalPrice()); - $this->assertEquals(new Price('0', 'USD'), $order->getTotalPaid()); $collected_adjustments = $order->collectAdjustments(); $this->assertEquals(new Price('10.00', 'USD'), $collected_adjustments[2]->getAmount()); + $this->assertEquals(new Price('27.00', 'USD'), $order->getTotalPrice()); + $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); + + // Test that payments update the order total paid and balance. + $order->save(); + $payment = Payment::create([ + 'order_id' => $order->id(), + 'amount' => new Price('25.00', 'USD'), + 'payment_gateway' => 'example', + 'state' => 'capture_completed', + ]); + $payment->save(); + $order = Order::load($order->id()); + $this->assertEquals(new Price('2.00', 'USD'), $order->getBalance()); + $payment->setRefundedAmount(new Price('5.00', 'USD'))->save(); + $order = Order::load($order->id()); + $this->assertEquals(new Price('7.00', 'USD'), $order->getBalance()); + $payment->delete(); + $order = Order::load($order->id()); + $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); + $payment2 = Payment::create([ + 'order_id' => $order->id(), + 'amount' => new Price('27.00', 'USD'), + 'payment_gateway' => 'example', + 'state' => 'capture_completed', + ]); + $payment2->save(); + $order = Order::load($order->id()); + $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); + + // Test that payments can be partially refunded multiple times. + /** @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface $payment_gateway_plugin */ + $payment_gateway_plugin = $this->payment_gateway->getPlugin(); + + $payment_gateway_plugin->refundPayment($payment2, new Price('17.00', 'USD')); + $order = Order::load($order->id()); + $this->assertEquals(new Price('17.00', 'USD'), $order->getBalance()); + + $payment_gateway_plugin->refundPayment($payment2, new Price('5.00', 'USD')); + $order = Order::load($order->id()); + $this->assertEquals(new Price('22.00', 'USD'), $order->getBalance()); + + // Test that the total paid amount can be set explicitly on the order. + $order->setTotalPaid(new Price('0.00', 'USD')); + $order->save(); + $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); // Test that payments update the order total paid and balance. $order->save(); diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php index 873c04eaf7..2949715c6d 100644 --- a/modules/payment/src/Entity/Payment.php +++ b/modules/payment/src/Entity/Payment.php @@ -280,7 +280,9 @@ public function preSave(EntityStorageInterface $storage) { $this->getOrder()->addPayment($this->getAmount())->save(); } else { - $this->getOrder()->subtractPayment($this->getRefundedAmount())->save(); + $original = $this->values['original']; + $net_refund = $this->getRefundedAmount()->subtract($original->getRefundedAmount()); + $this->getOrder()->subtractPayment($net_refund)->save(); } } From 7593adecc23bae0774888c541db8b1183a6cce80 Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Wed, 8 Feb 2017 09:16:06 -0800 Subject: [PATCH 06/18] Clean up Order entity test. --- .../tests/src/Kernel/Entity/OrderTest.php | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index a746a5e955..3fba039bfb 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -30,9 +30,11 @@ class OrderTest extends CommerceKernelTestBase { protected $user; /** - * @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface; + * The payment gateway plugin. + * + * @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface */ - protected $payment_gateway; + protected $payment_gateway_plugin; /** * Modules to enable. @@ -74,7 +76,7 @@ protected function setUp() { 'plugin' => 'example_onsite', ]); $payment_gateway->save(); - $this->payment_gateway = $payment_gateway; + $this->payment_gateway_plugin = $payment_gateway->getPlugin(); $user = $this->createUser(); $this->user = $this->reloadEntity($user); @@ -254,7 +256,7 @@ public function testOrder() { $payment->save(); $order = Order::load($order->id()); $this->assertEquals(new Price('2.00', 'USD'), $order->getBalance()); - $payment->setRefundedAmount(new Price('5.00', 'USD'))->save(); + $this->payment_gateway_plugin->refundPayment($payment, new Price('5.00', 'USD')); $order = Order::load($order->id()); $this->assertEquals(new Price('7.00', 'USD'), $order->getBalance()); $payment->delete(); @@ -271,14 +273,10 @@ public function testOrder() { $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); // Test that payments can be partially refunded multiple times. - /** @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface $payment_gateway_plugin */ - $payment_gateway_plugin = $this->payment_gateway->getPlugin(); - - $payment_gateway_plugin->refundPayment($payment2, new Price('17.00', 'USD')); + $this->payment_gateway_plugin->refundPayment($payment2, new Price('17.00', 'USD')); $order = Order::load($order->id()); $this->assertEquals(new Price('17.00', 'USD'), $order->getBalance()); - - $payment_gateway_plugin->refundPayment($payment2, new Price('5.00', 'USD')); + $this->payment_gateway_plugin->refundPayment($payment2, new Price('5.00', 'USD')); $order = Order::load($order->id()); $this->assertEquals(new Price('22.00', 'USD'), $order->getBalance()); @@ -287,7 +285,7 @@ public function testOrder() { $order->save(); $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); - // Test that payments update the order total paid and balance. + // Test that deleted payments update the order total paid and balance. $order->save(); $payment = Payment::create([ 'order_id' => $order->id(), @@ -298,25 +296,9 @@ public function testOrder() { $order = Order::load($order->id()); $this->assertEquals(new Price('25.00', 'USD'), $order->getTotalPaid()); $this->assertEquals(new Price('2.00', 'USD'), $order->getBalance()); - $payment->setRefundedAmount(new Price('5.00', 'USD'))->save(); - $order = Order::load($order->id()); - $this->assertEquals(new Price('20.00', 'USD'), $order->getTotalPaid()); - $this->assertEquals(new Price('7.00', 'USD'), $order->getBalance()); $payment->delete(); $order = Order::load($order->id()); $this->assertEquals(new Price('0.00', 'USD'), $order->getTotalPaid()); - $payment2 = Payment::create([ - 'order_id' => $order->id(), - 'amount' => new Price('27.00', 'USD'), - 'payment_gateway' => 'example', - ]); - $payment2->save(); - $order = Order::load($order->id()); - $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); - // Test that the total paid amount can be set explicitly on the order. - $order->setTotalPaid(new Price('0.00', 'USD')); - $order->save(); - $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); $this->assertEquals('completed', $order->getState()->value); From 913b912c2d62cfb61cd73a575a768daffc8428ad Mon Sep 17 00:00:00 2001 From: Steve Oliver Date: Wed, 12 Apr 2017 14:01:59 -0700 Subject: [PATCH 07/18] Use Price. --- modules/order/src/Entity/Order.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/order/src/Entity/Order.php b/modules/order/src/Entity/Order.php index 573c9a0957..44a0dbf8ab 100644 --- a/modules/order/src/Entity/Order.php +++ b/modules/order/src/Entity/Order.php @@ -3,6 +3,7 @@ namespace Drupal\commerce_order\Entity; use Drupal\commerce_order\Adjustment; +use Drupal\commerce_price\Price; use Drupal\commerce_store\Entity\StoreInterface; use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityChangedTrait; From 14a37fdea1012bc02d86abb0d8c2616c805b85fd Mon Sep 17 00:00:00 2001 From: vasike Date: Mon, 4 Sep 2017 18:28:57 -0500 Subject: [PATCH 08/18] add update to add the total_paid field --- modules/order/commerce_order.post_update.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/order/commerce_order.post_update.php b/modules/order/commerce_order.post_update.php index 335c6cf399..0be9b15dad 100644 --- a/modules/order/commerce_order.post_update.php +++ b/modules/order/commerce_order.post_update.php @@ -5,6 +5,8 @@ * Post update functions for Order. */ +use Drupal\Core\Field\BaseFieldDefinition; + /** * Revert Order views to fix broken Price fields. */ @@ -149,3 +151,18 @@ function commerce_order_post_update_5() { return $message; } + +/** + * Add 'total_paid' field to 'commerce_order' entities. + */ +function commerce_order_post_update_6() { + $storage_definition = BaseFieldDefinition::create('commerce_price') + ->setLabel(t('Total paid')) + ->setDescription(t('The total amount paid on the order.')) + ->setReadOnly(TRUE) + ->setDisplayConfigurable('form', FALSE) + ->setDisplayConfigurable('view', TRUE); + \Drupal::entityDefinitionUpdateManager() + ->installFieldStorageDefinition('total_paid', 'commerce_order', 'commerce_order', $storage_definition); + return t('The order total paid field was created.'); +} From f1678e15128252ce805cfd981b727b36a9c12cc6 Mon Sep 17 00:00:00 2001 From: Joaquin Bravo Date: Thu, 7 Sep 2017 17:08:03 -0500 Subject: [PATCH 09/18] Update payment states to match new names --- modules/order/tests/src/Kernel/Entity/OrderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index c5bf320c6f..8d7d8f34b2 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -260,7 +260,7 @@ public function testOrder() { 'order_id' => $order->id(), 'amount' => new Price('25.00', 'USD'), 'payment_gateway' => 'example', - 'state' => 'capture_completed', + 'state' => 'completed', ]); $payment->save(); $order = Order::load($order->id()); @@ -275,7 +275,7 @@ public function testOrder() { 'order_id' => $order->id(), 'amount' => new Price('27.00', 'USD'), 'payment_gateway' => 'example', - 'state' => 'capture_completed', + 'state' => 'completed', ]); $payment2->save(); $order = Order::load($order->id()); From 0db777ae127806b4c30c33d13bb14a8fdb3cdb6a Mon Sep 17 00:00:00 2001 From: Joaquin Bravo Contreras Date: Mon, 11 Sep 2017 01:28:00 -0400 Subject: [PATCH 10/18] Watch payment status before changing order balance --- modules/order/tests/src/Kernel/Entity/OrderTest.php | 9 ++++++++- modules/payment/src/Entity/Payment.php | 13 +++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index 8d7d8f34b2..d21995e8a4 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -294,7 +294,7 @@ public function testOrder() { $order->save(); $this->assertEquals(new Price('27.00', 'USD'), $order->getBalance()); - // Test that deleted payments update the order total paid and balance. + // Test that payments only substract total when setting to completed. $order->save(); $payment = Payment::create([ 'order_id' => $order->id(), @@ -303,8 +303,15 @@ public function testOrder() { ]); $payment->save(); $order = Order::load($order->id()); + $this->assertEquals(new Price('0.00', 'USD'), $order->getTotalPaid()); + + $payment->setState('completed'); + $payment->save(); + $order = Order::load($order->id()); $this->assertEquals(new Price('25.00', 'USD'), $order->getTotalPaid()); $this->assertEquals(new Price('2.00', 'USD'), $order->getBalance()); + + // Test that deleted payments update the order total paid and balance. $payment->delete(); $order = Order::load($order->id()); $this->assertEquals(new Price('0.00', 'USD'), $order->getTotalPaid()); diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php index 460d236e2c..cc77431ebb 100644 --- a/modules/payment/src/Entity/Payment.php +++ b/modules/payment/src/Entity/Payment.php @@ -291,7 +291,8 @@ public function preSave(EntityStorageInterface $storage) { $refunded_amount = new Price('0', $this->getAmount()->getCurrencyCode()); $this->setRefundedAmount($refunded_amount); } - // Maintain the authorized completed timestamps. + // Maintain the authorized completed timestamps while also maintaining the + // order balance. $state = $this->getState()->value; $original_state = isset($this->original) ? $this->original->getState()->value : ''; if ($state == 'authorized' && $original_state != 'authorized') { @@ -300,16 +301,12 @@ public function preSave(EntityStorageInterface $storage) { } } if ($state == 'completed' && $original_state != 'completed') { + $this->getOrder()->addPayment($this->getAmount())->save(); if (empty($this->getCompletedTime())) { $this->setCompletedTime(\Drupal::time()->getRequestTime()); } - } - - // Add or subtract payments from order. - if ($this->isNew()) { - $this->getOrder()->addPayment($this->getAmount())->save(); - } - else { + } else if (in_array($state, ['partially_refunded', 'refunded']) && + in_array($original_state, ['completed', 'partially_refunded'])) { $original = $this->values['original']; $net_refund = $this->getRefundedAmount()->subtract($original->getRefundedAmount()); $this->getOrder()->subtractPayment($net_refund)->save(); From 5395dc2f0afd00c21763068340a95aee5efbd116 Mon Sep 17 00:00:00 2001 From: Joaquin Bravo Contreras Date: Mon, 11 Sep 2017 08:40:22 -0400 Subject: [PATCH 11/18] Fix two coding standard errors --- modules/payment/src/Entity/Payment.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php index cc77431ebb..89602ab812 100644 --- a/modules/payment/src/Entity/Payment.php +++ b/modules/payment/src/Entity/Payment.php @@ -305,7 +305,8 @@ public function preSave(EntityStorageInterface $storage) { if (empty($this->getCompletedTime())) { $this->setCompletedTime(\Drupal::time()->getRequestTime()); } - } else if (in_array($state, ['partially_refunded', 'refunded']) && + } + elseif (in_array($state, ['partially_refunded', 'refunded']) && in_array($original_state, ['completed', 'partially_refunded'])) { $original = $this->values['original']; $net_refund = $this->getRefundedAmount()->subtract($original->getRefundedAmount()); From 91c03f02bce66eafe624e7d552cf03defcb9686c Mon Sep 17 00:00:00 2001 From: steveoliver Date: Fri, 15 Sep 2017 14:03:04 -0500 Subject: [PATCH 12/18] remove the word 'remaining' -- it seems redundant --- modules/order/src/Entity/OrderInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/order/src/Entity/OrderInterface.php b/modules/order/src/Entity/OrderInterface.php index fe6f369883..98db0fbf5b 100644 --- a/modules/order/src/Entity/OrderInterface.php +++ b/modules/order/src/Entity/OrderInterface.php @@ -310,7 +310,7 @@ public function getTotalPaid(); public function setTotalPaid(Price $amount); /** - * Gets the remaining amount unpaid on the order. + * Gets the amount unpaid on the order. * * @return \Drupal\commerce_price\Price|null * The total order amount minus the total paid, or NULL. From 2d4fbeda725253d78dea6b8bbdad4107510906a6 Mon Sep 17 00:00:00 2001 From: Harings Rob Date: Fri, 15 Sep 2017 17:30:55 -0500 Subject: [PATCH 13/18] #2856586: Add PaymentEvent class --- modules/payment/src/Event/PaymentEvent.php | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 modules/payment/src/Event/PaymentEvent.php diff --git a/modules/payment/src/Event/PaymentEvent.php b/modules/payment/src/Event/PaymentEvent.php new file mode 100644 index 0000000000..fec57891ec --- /dev/null +++ b/modules/payment/src/Event/PaymentEvent.php @@ -0,0 +1,42 @@ +payment = $payment; + } + + /** + * Gets the payment. + * + * @return \Drupal\commerce_payment\Entity\PaymentInterface + * Gets the payment. + */ + public function getEntity() { + return $this->payment; + } + +} From d9c0e39bd73c9161898997e2def9428a8382426b Mon Sep 17 00:00:00 2001 From: Joaquin Bravo Contreras Date: Fri, 15 Sep 2017 17:33:28 -0500 Subject: [PATCH 14/18] Issue #2856586 by haringsrob, jackbravo: Dispatch PAYMENT_ORDER_PAID_IN_FULL event --- modules/payment/src/Entity/Payment.php | 5 +++++ modules/payment/src/Event/PaymentEvents.php | 9 +++++++++ modules/payment/src/PaymentStorage.php | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php index de50f0df1d..d7a5eab6db 100644 --- a/modules/payment/src/Entity/Payment.php +++ b/modules/payment/src/Entity/Payment.php @@ -2,6 +2,8 @@ namespace Drupal\commerce_payment\Entity; +use Drupal\commerce_payment\Event\PaymentEvents; +use Drupal\commerce_payment\PaymentStorage; use Drupal\commerce_price\Price; use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityMalformedException; @@ -304,6 +306,9 @@ public function preSave(EntityStorageInterface $storage) { if (empty($this->getCompletedTime())) { $this->setCompletedTime(\Drupal::time()->getRequestTime()); } + if ($this->getOrder()->getBalance()->isZero() && $storage instanceof PaymentStorage) { + $storage->dispatchPaymentEvent($this, PaymentEvents::PAYMENT_ORDER_PAID_IN_FULL); + } } elseif (in_array($state, ['partially_refunded', 'refunded']) && in_array($original_state, ['completed', 'partially_refunded'])) { diff --git a/modules/payment/src/Event/PaymentEvents.php b/modules/payment/src/Event/PaymentEvents.php index 9a4abfeab3..91fb47b0bc 100644 --- a/modules/payment/src/Event/PaymentEvents.php +++ b/modules/payment/src/Event/PaymentEvents.php @@ -13,4 +13,13 @@ final class PaymentEvents { */ const FILTER_PAYMENT_GATEWAYS = 'commerce_payment.filter_payment_gateways'; + /** + * Name of the event fired after paying an order in full. + * + * @Event + * + * @see \Drupal\commerce_payment\Event\PaymentEvent + */ + const PAYMENT_ORDER_PAID_IN_FULL = 'commerce_payment.order_paid_in_full'; + } diff --git a/modules/payment/src/PaymentStorage.php b/modules/payment/src/PaymentStorage.php index f59d567fed..e8f0799558 100644 --- a/modules/payment/src/PaymentStorage.php +++ b/modules/payment/src/PaymentStorage.php @@ -4,6 +4,9 @@ use Drupal\commerce\CommerceContentEntityStorage; use Drupal\commerce_order\Entity\OrderInterface; +use Drupal\commerce_payment\Entity\PaymentInterface; +use Drupal\commerce_payment\Event\PaymentEvent; +use Drupal\commerce_payment\Event\PaymentEvents; use Drupal\Core\Entity\EntityStorageException; /** @@ -56,4 +59,18 @@ protected function doCreate(array $values) { return parent::doCreate($values); } + /** + * Notifies other modules about payment events. + * + * @param \Drupal\commerce_payment\Entity\PaymentInterface $payment + * The payment. + * @param string $event_id + * The event identifier defined in + * \Drupal\commerce_payment\Event\PaymentEvents. + */ + public function dispatchPaymentEvent(PaymentInterface $payment, $event_id) { + $event = new PaymentEvent($payment); + $this->eventDispatcher->dispatch($event_id, $event); + } + } From 5ba843bcaa1030eb8fb284be93430d160862d674 Mon Sep 17 00:00:00 2001 From: Harings Rob Date: Fri, 15 Sep 2017 19:44:54 -0500 Subject: [PATCH 15/18] Issue #2856586 by haringsrob, jackbravo: add payment_events_test helper module --- .../payment_events_test.info.yml | 4 ++ .../payment_events_test.services.yml | 6 +++ .../src/EventSubscriber.php | 52 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 modules/payment/tests/modules/payment_events_test/payment_events_test.info.yml create mode 100644 modules/payment/tests/modules/payment_events_test/payment_events_test.services.yml create mode 100644 modules/payment/tests/modules/payment_events_test/src/EventSubscriber.php diff --git a/modules/payment/tests/modules/payment_events_test/payment_events_test.info.yml b/modules/payment/tests/modules/payment_events_test/payment_events_test.info.yml new file mode 100644 index 0000000000..604ef369cc --- /dev/null +++ b/modules/payment/tests/modules/payment_events_test/payment_events_test.info.yml @@ -0,0 +1,4 @@ +name: 'Configuration events test' +type: module +package: Testing +core: 8.x diff --git a/modules/payment/tests/modules/payment_events_test/payment_events_test.services.yml b/modules/payment/tests/modules/payment_events_test/payment_events_test.services.yml new file mode 100644 index 0000000000..a7df3aa96c --- /dev/null +++ b/modules/payment/tests/modules/payment_events_test/payment_events_test.services.yml @@ -0,0 +1,6 @@ +services: + payment_events_test.event_subscriber: + class: Drupal\payment_events_test\EventSubscriber + arguments: ['@state'] + tags: + - { name: event_subscriber } diff --git a/modules/payment/tests/modules/payment_events_test/src/EventSubscriber.php b/modules/payment/tests/modules/payment_events_test/src/EventSubscriber.php new file mode 100644 index 0000000000..fb49a6d3dd --- /dev/null +++ b/modules/payment/tests/modules/payment_events_test/src/EventSubscriber.php @@ -0,0 +1,52 @@ +state = $state; + } + + /** + * Reacts to payment event. + * + * @param \Drupal\commerce_payment\Event\PaymentEvent $event + * The payment event. + * @param string $name + * The name of the event. + */ + public function paymentEvent(PaymentEvent $event, $name) { + $this->state->set('payment_events_test.event', [ + 'event_name' => $name, + 'event_entity' => $event->getEntity(), + ]); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[PaymentEvents::PAYMENT_ORDER_PAID_IN_FULL][] = ['paymentEvent']; + return $events; + } + +} From 7e0c9a1db3930d6fe0ccb2be9f853f1c418366fc Mon Sep 17 00:00:00 2001 From: Harings Rob Date: Fri, 15 Sep 2017 19:45:27 -0500 Subject: [PATCH 16/18] Issue #2856586 by haringsrob, jackbravo: add PaymentEventsTest testing the paid in full event --- .../tests/src/Kernel/PaymentEventsTest.php | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 modules/payment/tests/src/Kernel/PaymentEventsTest.php diff --git a/modules/payment/tests/src/Kernel/PaymentEventsTest.php b/modules/payment/tests/src/Kernel/PaymentEventsTest.php new file mode 100644 index 0000000000..fa131e9a1a --- /dev/null +++ b/modules/payment/tests/src/Kernel/PaymentEventsTest.php @@ -0,0 +1,127 @@ +installEntitySchema('profile'); + $this->installEntitySchema('commerce_order'); + $this->installEntitySchema('commerce_order_item'); + $this->installEntitySchema('commerce_payment'); + $this->installEntitySchema('commerce_payment_method'); + $this->installConfig('commerce_order'); + $this->installConfig('commerce_payment'); + + // An order item type that doesn't need a purchasable entity, for simplicity. + OrderItemType::create([ + 'id' => 'test', + 'label' => 'Test', + 'orderType' => 'default', + ])->save(); + + $payment_gateway = PaymentGateway::create([ + 'id' => 'example', + 'label' => 'Example', + 'plugin' => 'example_onsite', + ]); + $payment_gateway->save(); + + $user = $this->createUser(); + + /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */ + $payment_method_active = PaymentMethod::create([ + 'type' => 'credit_card', + 'payment_gateway' => 'example', + // Thu, 16 Jan 2020. + 'expires' => '1579132800', + 'uid' => $user->id(), + ]); + $payment_method_active->save(); + } + + /** + * Tests the basic payment events. + */ + public function testPaymentEvents() { + /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ + $order_item = OrderItem::create([ + 'type' => 'test', + 'quantity' => '1', + 'unit_price' => new Price('39.99', 'USD'), + ]); + $order_item->save(); + $order_item = $this->reloadEntity($order_item); + $order = Order::create([ + 'type' => 'default', + 'state' => 'completed', + 'store_id' => $this->store->id(), + ]); + $order->setItems([$order_item]); + $order->save(); + + $this->assertEquals(new Price('39.99', 'USD'), $order->getTotalPrice()); + $this->assertEquals(new Price('39.99', 'USD'), $order->getBalance()); + + // Create a dummy payment. + $payment = Payment::create([ + 'order_id' => $order->id(), + 'payment_gateway' => 'example', + 'payment_method' => 'credit_card', + 'remote_id' => '123456', + 'amount' => [ + 'number' => '39.99', + 'currency_code' => 'USD', + ], + 'state' => 'completed', + 'test' => TRUE, + ]); + $payment->save(); + + $order = $this->reloadEntity($order); + $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); + + // Check the paid in full event. + $event_recorder = \Drupal::state()->get('payment_events_test.event', FALSE); + $this->assertEquals(PaymentEvents::PAYMENT_ORDER_PAID_IN_FULL, $event_recorder['event_name']); + $this->assertEquals($payment->id(), $event_recorder['event_entity']->id()); + } + +} From 451f48740e4a2ffc8ab0aad6e62eb56bc1c71174 Mon Sep 17 00:00:00 2001 From: Joaquin Bravo Contreras Date: Mon, 25 Sep 2017 11:17:09 -0500 Subject: [PATCH 17/18] Remove unused use statement --- modules/payment/src/PaymentStorage.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/payment/src/PaymentStorage.php b/modules/payment/src/PaymentStorage.php index e8f0799558..b7663943bf 100644 --- a/modules/payment/src/PaymentStorage.php +++ b/modules/payment/src/PaymentStorage.php @@ -6,7 +6,6 @@ use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\Entity\PaymentInterface; use Drupal\commerce_payment\Event\PaymentEvent; -use Drupal\commerce_payment\Event\PaymentEvents; use Drupal\Core\Entity\EntityStorageException; /** From 73264300daac20bc34ac3751980c9e42885fba2d Mon Sep 17 00:00:00 2001 From: Joaquin Bravo Contreras Date: Tue, 7 Nov 2017 16:46:13 -0600 Subject: [PATCH 18/18] Fix coding standard errors --- modules/order/tests/src/Kernel/Entity/OrderTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php index 0e4fdf9c21..420e506dc1 100644 --- a/modules/order/tests/src/Kernel/Entity/OrderTest.php +++ b/modules/order/tests/src/Kernel/Entity/OrderTest.php @@ -286,7 +286,6 @@ public function testOrder() { unset($multiplied_order_item_adjustments[0]); $this->assertEquals(array_merge($multiplied_order_item_adjustments, $adjustments), $order->collectAdjustments()); - // getBalance $this->assertEquals(new Price('31.00', 'USD'), $order->getTotalPrice()); $this->assertEquals(new Price('31.00', 'USD'), $order->getBalance()); @@ -351,7 +350,6 @@ public function testOrder() { $payment->delete(); $order = Order::load($order->id()); $this->assertEquals(new Price('0.00', 'USD'), $order->getTotalPaid()); - // finish getBalance $this->assertEquals('completed', $order->getState()->value);