Skip to content

Instantly share code, notes, and snippets.

@xuxucode
Last active January 13, 2018 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xuxucode/568ec15d843fd1b1dc7eefc1417e6817 to your computer and use it in GitHub Desktop.
Save xuxucode/568ec15d843fd1b1dc7eefc1417e6817 to your computer and use it in GitHub Desktop.
Order paid in full from tag 2.3
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