Last active
January 13, 2018 10:19
-
-
Save xuxucode/568ec15d843fd1b1dc7eefc1417e6817 to your computer and use it in GitHub Desktop.
Order paid in full from tag 2.3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/modules/order/commerce_order.post_update.php b/modules/order/commerce_order.post_update.php | |
index 26dd36c..7c47d89 100644 | |
--- a/modules/order/commerce_order.post_update.php | |
+++ b/modules/order/commerce_order.post_update.php | |
@@ -7,6 +7,8 @@ | |
use Drupal\Core\Entity\Entity\EntityFormDisplay; | |
+use Drupal\Core\Field\BaseFieldDefinition; | |
+ | |
/** | |
* Revert Order views to fix broken Price fields. | |
*/ | |
@@ -170,3 +172,18 @@ function commerce_order_post_update_6() { | |
} | |
} | |
} | |
+ | |
+/** | |
+ * Add 'total_paid' field to 'commerce_order' entities. | |
+ */ | |
+function commerce_order_post_update_7() { | |
+ $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.'); | |
+} | |
diff --git a/modules/order/src/Entity/Order.php b/modules/order/src/Entity/Order.php | |
index 99eb6be..d95659d 100644 | |
--- a/modules/order/src/Entity/Order.php | |
+++ b/modules/order/src/Entity/Order.php | |
@@ -4,6 +4,7 @@ namespace Drupal\commerce_order\Entity; | |
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; | |
@@ -393,6 +394,49 @@ class Order extends CommerceContentEntityBase implements OrderInterface { | |
/** | |
* {@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} | |
+ */ | |
public function getState() { | |
return $this->get('state')->first(); | |
} | |
@@ -671,6 +715,13 @@ class Order extends CommerceContentEntityBase implements OrderInterface { | |
->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 ba21dc2..1edd1e5 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; | |
@@ -273,6 +274,50 @@ interface OrderInterface extends ContentEntityInterface, EntityAdjustableInterfa | |
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 | |
+ * The amount to set as the order total paid. | |
+ */ | |
+ public function setTotalPaid(Price $amount); | |
+ | |
+ /** | |
+ * Gets the 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. | |
* | |
* @return \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface | |
diff --git a/modules/order/tests/src/Kernel/Entity/OrderTest.php b/modules/order/tests/src/Kernel/Entity/OrderTest.php | |
index a7bff3d..b6236f4 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\OrderItem; | |
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 @@ class OrderTest extends CommerceKernelTestBase { | |
$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,13 @@ class OrderTest extends CommerceKernelTestBase { | |
'orderType' => 'default', | |
])->save(); | |
+ $payment_gateway = PaymentGateway::create([ | |
+ 'id' => 'example', | |
+ 'label' => 'Example', | |
+ 'plugin' => 'example_onsite', | |
+ ]); | |
+ $payment_gateway->save(); | |
+ | |
$user = $this->createUser(); | |
$this->user = $this->reloadEntity($user); | |
} | |
@@ -96,6 +108,11 @@ class OrderTest extends CommerceKernelTestBase { | |
* @covers ::getSubtotalPrice | |
* @covers ::recalculateTotalPrice | |
* @covers ::getTotalPrice | |
+ * @covers ::getBalance | |
+ * @covers ::addPayment | |
+ * @covers ::subtractPayment | |
+ * @covers ::setTotalPaid | |
+ * @covers ::getTotalPaid | |
* @covers ::getState | |
* @covers ::getRefreshState | |
* @covers ::setRefreshState | |
@@ -138,6 +155,7 @@ class OrderTest extends CommerceKernelTestBase { | |
$order = Order::create([ | |
'type' => 'default', | |
'state' => 'completed', | |
+ 'store_id' => $this->store->id(), | |
]); | |
$order->save(); | |
@@ -183,6 +201,7 @@ class OrderTest extends CommerceKernelTestBase { | |
$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', | |
@@ -213,6 +232,7 @@ class OrderTest extends CommerceKernelTestBase { | |
$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], $adjustments[2]], $order->getAdjustments()); | |
$order->setAdjustments($adjustments); | |
$this->assertEquals($adjustments, $order->getAdjustments()); | |
@@ -250,6 +270,70 @@ class OrderTest extends CommerceKernelTestBase { | |
$collected_adjustments = $order->collectAdjustments(); | |
$this->assertEquals($multiplied_order_item_adjustments[0], $collected_adjustments[0]); | |
$this->assertEquals($multiplied_order_item_adjustments[1], $collected_adjustments[1]); | |
+ $order->addPayment(new Price('20.00', 'USD')); | |
+ $this->assertEquals(new Price('20.00', 'USD'), $order->getTotalPaid()); | |
+ $this->assertEquals(new Price('21.00', 'USD'), $order->getBalance()); | |
+ $order->subtractPayment(new Price('5.00', 'USD')); | |
+ $this->assertEquals(new Price('15.00', 'USD'), $order->getTotalPaid()); | |
+ $this->assertEquals(new Price('26.00', 'USD'), $order->getBalance()); | |
+ $order->setTotalPaid(new Price('41.00', 'USD')); | |
+ $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); | |
+ $order->setTotalPaid(new Price('0.00', 'USD')); | |
+ $this->assertEquals(new Price('41.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' => 'completed', | |
+ ]); | |
+ $payment->save(); | |
+ $order = Order::load($order->id()); | |
+ $this->assertEquals(new Price('25.00', 'USD'), $order->getTotalPaid()); | |
+ $this->assertEquals(new Price('16.00', 'USD'), $order->getBalance()); | |
+ $payment->setState('partially_refunded')->save(); | |
+ $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('21.00', 'USD'), $order->getBalance()); | |
+ $payment->delete(); | |
+ $order = Order::load($order->id()); | |
+ $this->assertEquals(new Price('0.00', 'USD'), $order->getTotalPaid()); | |
+ $this->assertEquals(new Price('41.00', 'USD'), $order->getBalance()); | |
+ $payment2 = Payment::create([ | |
+ 'order_id' => $order->id(), | |
+ 'amount' => new Price('41.00', 'USD'), | |
+ 'payment_gateway' => 'example', | |
+ 'state' => 'completed', | |
+ ]); | |
+ $payment2->save(); | |
+ $order = Order::load($order->id()); | |
+ $this->assertEquals(new Price('41.00', 'USD'), $order->getTotalPaid()); | |
+ $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); | |
+ | |
+ // Test that payments not in completed or partially_refunded state do not affect order balance. | |
+ $payment3 = Payment::create([ | |
+ 'order_id' => $order->id(), | |
+ 'amount' => new Price('25.00', 'USD'), | |
+ 'payment_gateway' => 'example', | |
+ 'state' => 'new', | |
+ ]); | |
+ $payment3->save(); | |
+ $order = Order::load($order->id()); | |
+ $this->assertEquals(new Price('41.00', 'USD'), $order->getTotalPaid()); | |
+ $this->assertEquals(new Price('0.00', 'USD'), $order->getBalance()); | |
+ $payment3->delete(); | |
+ $order = Order::load($order->id()); | |
+ $this->assertEquals(new Price('41.00', 'USD'), $order->getTotalPaid()); | |
+ $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('41.00', 'USD'), $order->getBalance()); | |
+ | |
// Confirm that locked adjustments persist after clear. | |
// Custom adjustments are locked by default. | |
$order->setAdjustments($adjustments); | |
diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php | |
index ed56bde..af05c78 100644 | |
--- a/modules/payment/src/Entity/Payment.php | |
+++ b/modules/payment/src/Entity/Payment.php | |
@@ -2,6 +2,7 @@ | |
namespace Drupal\commerce_payment\Entity; | |
+use Drupal\commerce_payment\Event\PaymentEvents; | |
use Drupal\commerce_price\Price; | |
use Drupal\Core\Entity\ContentEntityBase; | |
use Drupal\Core\Entity\EntityMalformedException; | |
@@ -297,7 +298,8 @@ class Payment extends ContentEntityBase implements PaymentInterface { | |
$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') { | |
@@ -306,9 +308,33 @@ class Payment extends ContentEntityBase implements PaymentInterface { | |
} | |
} | |
if ($state == 'completed' && $original_state != 'completed') { | |
+ $this->getOrder()->addPayment($this->getAmount())->save(); | |
if (empty($this->getCompletedTime())) { | |
$this->setCompletedTime(\Drupal::time()->getRequestTime()); | |
} | |
+ if ($this->getOrder()->getBalance()->isZero()) { | |
+ $storage->dispatchPaymentEvent($this, PaymentEvents::PAYMENT_ORDER_PAID_IN_FULL); | |
+ } | |
+ } | |
+ 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()); | |
+ $this->getOrder()->subtractPayment($net_refund)->save(); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * {@inheritdoc} | |
+ */ | |
+ public static function preDelete(EntityStorageInterface $storage, array $entities) { | |
+ parent::preDelete($storage, $entities); | |
+ | |
+ // Subtract each payment from order. | |
+ foreach ($entities as $payment) { | |
+ if (in_array($payment->getState()->value, ['completed', 'partially_refunded'])) { | |
+ $payment->getOrder()->subtractPayment($payment->getBalance())->save(); | |
+ } | |
} | |
} | |
diff --git a/modules/payment/src/Event/PaymentEvent.php b/modules/payment/src/Event/PaymentEvent.php | |
new file mode 100644 | |
index 0000000..fec5789 | |
--- /dev/null | |
+++ b/modules/payment/src/Event/PaymentEvent.php | |
@@ -0,0 +1,42 @@ | |
+<?php | |
+ | |
+namespace Drupal\commerce_payment\Event; | |
+ | |
+use Drupal\commerce_payment\Entity\PaymentInterface; | |
+use Symfony\Component\EventDispatcher\Event; | |
+ | |
+/** | |
+ * Defines the payment event. | |
+ * | |
+ * @see \Drupal\commerce_payment\Event\PaymentEvents | |
+ */ | |
+class PaymentEvent extends Event { | |
+ | |
+ /** | |
+ * The payment. | |
+ * | |
+ * @var \Drupal\commerce_payment\Entity\PaymentInterface | |
+ */ | |
+ protected $payment; | |
+ | |
+ /** | |
+ * Constructs a new Paymentevent. | |
+ * | |
+ * @param \Drupal\commerce_payment\Entity\PaymentInterface $payment | |
+ * The payment. | |
+ */ | |
+ public function __construct(PaymentInterface $payment) { | |
+ $this->payment = $payment; | |
+ } | |
+ | |
+ /** | |
+ * Gets the payment. | |
+ * | |
+ * @return \Drupal\commerce_payment\Entity\PaymentInterface | |
+ * Gets the payment. | |
+ */ | |
+ public function getEntity() { | |
+ return $this->payment; | |
+ } | |
+ | |
+} | |
diff --git a/modules/payment/src/Event/PaymentEvents.php b/modules/payment/src/Event/PaymentEvents.php | |
index 9a4abfe..91fb47b 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 f59d567..b766394 100644 | |
--- a/modules/payment/src/PaymentStorage.php | |
+++ b/modules/payment/src/PaymentStorage.php | |
@@ -4,6 +4,8 @@ namespace Drupal\commerce_payment; | |
use Drupal\commerce\CommerceContentEntityStorage; | |
use Drupal\commerce_order\Entity\OrderInterface; | |
+use Drupal\commerce_payment\Entity\PaymentInterface; | |
+use Drupal\commerce_payment\Event\PaymentEvent; | |
use Drupal\Core\Entity\EntityStorageException; | |
/** | |
@@ -56,4 +58,18 @@ class PaymentStorage extends CommerceContentEntityStorage implements PaymentStor | |
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); | |
+ } | |
+ | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment