diff --git a/modules/log/commerce_log.commerce_log_categories.yml b/modules/log/commerce_log.commerce_log_categories.yml
index b921707752..8ea861b096 100644
--- a/modules/log/commerce_log.commerce_log_categories.yml
+++ b/modules/log/commerce_log.commerce_log_categories.yml
@@ -5,3 +5,7 @@ commerce_cart:
commerce_order:
label: Order
entity_type: commerce_order
+
+commerce_payment:
+ label: Payment
+ entity_type: commerce_payment
diff --git a/modules/log/commerce_log.commerce_log_templates.yml b/modules/log/commerce_log.commerce_log_templates.yml
index a9aee4860d..8e0e2ba06d 100644
--- a/modules/log/commerce_log.commerce_log_templates.yml
+++ b/modules/log/commerce_log.commerce_log_templates.yml
@@ -27,3 +27,12 @@ order_assigned:
category: commerce_order
label: 'Order assigned'
template: '
The order was assigned to {{ user }}.
'
+
+payment_log:
+ category: commerce_payment
+ label: 'Payment log'
+ template: '{% if new %} New payment {% else %} Payment {% endif %} {% if remote_id %} {{ remote_id }} {% else %} {{ id }} {% endif %} {% if not previous_amount %} of {{ amount|commerce_price_format }} {% endif %} in state {{ state}} {% if previous_amount %} was changed from {{ previous_amount|commerce_price_format }} to {{ amount|commerce_price_format }} {% endif %}.
'
+payment_refunded:
+ category: commerce_payment
+ label: 'Payment refunded'
+ template: '{% if new %} New payment {% else %} Payment {% endif %} {% if remote_id %} {{ remote_id }} {% else %} {{ id }} {% endif %} was refunded {{ refunded_amount|commerce_price_format }} and is now in state {{ state }}.
'
diff --git a/modules/log/commerce_log.module b/modules/log/commerce_log.module
index 9c4927c624..79d0d105ca 100644
--- a/modules/log/commerce_log.module
+++ b/modules/log/commerce_log.module
@@ -5,6 +5,8 @@
* Provides activity logs for Commerce entities.
*/
+use Drupal\commerce_log\PaymentListBuilder;
+
/**
* Implements hook_preprocess_commerce_order().
*/
@@ -20,3 +22,13 @@ function commerce_log_preprocess_commerce_order(&$variables) {
'#title' => t('Order activity'),
];
}
+
+/**
+ * Implements hook_entity_type_alter() for commerce_payment.
+ */
+function commerce_log_entity_type_alter(array &$entity_types) {
+ if (isset($entity_types['commerce_payment'])) {
+ /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
+ $entity_types['commerce_payment']->setListBuilderClass(PaymentListBuilder::class);
+ }
+}
diff --git a/modules/log/config/install/views.view.commerce_activity.yml b/modules/log/config/install/views.view.commerce_activity.yml
index 05014113a5..9c7ce437f4 100644
--- a/modules/log/config/install/views.view.commerce_activity.yml
+++ b/modules/log/config/install/views.view.commerce_activity.yml
@@ -288,7 +288,7 @@ display:
entity_type: commerce_log
entity_field: log_id
plugin_id: standard
- title: 'Order logs'
+ title: 'Order activity'
header: { }
footer: { }
empty:
diff --git a/modules/log/src/CommerceLogServiceProvider.php b/modules/log/src/CommerceLogServiceProvider.php
index 769cc5fc21..2998d9623b 100644
--- a/modules/log/src/CommerceLogServiceProvider.php
+++ b/modules/log/src/CommerceLogServiceProvider.php
@@ -2,6 +2,7 @@
namespace Drupal\commerce_log;
+use Drupal\commerce_log\EventSubscriber\PaymentEventSubscriber;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;
@@ -29,6 +30,11 @@ public function register(ContainerBuilder $container) {
->addTag('event_subscriber')
->addArgument(new Reference('entity_type.manager'));
}
+ if (isset($modules['commerce_payment'])) {
+ $container->register('commerce_log.payment_subscriber', PaymentEventSubscriber::class)
+ ->addTag('event_subscriber')
+ ->addArgument(new Reference('entity_type.manager'));
+ }
}
}
diff --git a/modules/log/src/EventSubscriber/PaymentEventSubscriber.php b/modules/log/src/EventSubscriber/PaymentEventSubscriber.php
new file mode 100644
index 0000000000..d03840b093
--- /dev/null
+++ b/modules/log/src/EventSubscriber/PaymentEventSubscriber.php
@@ -0,0 +1,108 @@
+logStorage = $entity_type_manager->getStorage('commerce_log');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events = [
+ PaymentEvents::PAYMENT_PRESAVE => ['onPaymentPresave', -100],
+ PaymentEvents::PAYMENT_PREDELETE => ['onPaymentPredelete', -100],
+ ];
+ return $events;
+ }
+
+ /**
+ * Creates a log before a payment is saved.
+ *
+ * @param \Drupal\commerce_payment\Event\PaymentEvent $event
+ * The payment event.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ public function onPaymentPresave(PaymentEvent $event) {
+ $this->logPayment($event->getPayment());
+ }
+
+ /**
+ * Creates a log when a payment is deleted.
+ *
+ * @param \Drupal\commerce_payment\Event\PaymentEvent $event
+ * The payment event.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ public function onPaymentPredelete(PaymentEvent $event) {
+ $event->getPayment()->setState('deleted');
+ $this->logPayment($event->getPayment());
+ }
+
+ /**
+ * Creates a log when a payment is changed.
+ *
+ * @param \Drupal\commerce_payment\Entity\PaymentInterface $payment
+ * The payment.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ protected function logPayment(PaymentInterface $payment) {
+ $refund = $payment->getRefundedAmount();
+ $isNew = $payment->isNew();
+ $previousAmount = FALSE;
+ if (!empty($payment->original) && !$payment->getAmount()->equals($payment->original->getAmount())) {
+ $previousAmount = $payment->original->getAmount();
+ }
+ if ($refund && Calculator::trim($refund->getNumber())) {
+ if (!empty($payment->original) && !$payment->getRefundedAmount()->equals($payment->original->getRefundedAmount())) {
+ $refund = $payment->getRefundedAmount()->subtract($payment->original->getRefundedAmount());
+ }
+ $this->logStorage->generate($payment->getOrder(), 'payment_refunded', [
+ 'id' => $payment->id(),
+ 'remote_id' => $payment->getRemoteId(),
+ 'refunded_amount' => $refund,
+ 'state' => $payment->getState()->value,
+ 'new' => $isNew,
+ ])->save();
+ }
+ else {
+ $this->logStorage->generate($payment->getOrder(), 'payment_log', [
+ 'id' => $payment->id(),
+ 'remote_id' => $payment->getRemoteId(),
+ 'new' => $isNew,
+ 'amount' => $payment->getAmount(),
+ 'previous_amount' => $previousAmount,
+ 'state' => $payment->getState()->value,
+ ])->save();
+ }
+ }
+
+}
diff --git a/modules/log/src/PaymentListBuilder.php b/modules/log/src/PaymentListBuilder.php
new file mode 100644
index 0000000000..65d38b0401
--- /dev/null
+++ b/modules/log/src/PaymentListBuilder.php
@@ -0,0 +1,35 @@
+load();
+ /** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
+ $payment = reset($entities);
+ if ($payment) {
+ $build['log']['title'] = [
+ '#markup' => '' . $this->t('Order activity') . '
',
+ ];
+ $build['log']['activity'] = [
+ '#type' => 'view',
+ '#name' => 'commerce_activity',
+ '#display_id' => 'default',
+ '#arguments' => [$payment->getOrder()->id(), 'commerce_order'],
+ '#embed' => FALSE,
+ ];
+ }
+ return $build;
+ }
+
+}
diff --git a/modules/payment/src/Entity/Payment.php b/modules/payment/src/Entity/Payment.php
index f887f6f957..697473add7 100644
--- a/modules/payment/src/Entity/Payment.php
+++ b/modules/payment/src/Entity/Payment.php
@@ -26,6 +26,7 @@
* bundle_plugin_type = "commerce_payment_type",
* handlers = {
* "access" = "Drupal\commerce_payment\PaymentAccessControlHandler",
+ * "event" = "Drupal\commerce_payment\Event\PaymentEvent",
* "list_builder" = "Drupal\commerce_payment\PaymentListBuilder",
* "storage" = "Drupal\commerce_payment\PaymentStorage",
* "form" = {
diff --git a/modules/payment/src/Event/PaymentEvent.php b/modules/payment/src/Event/PaymentEvent.php
new file mode 100644
index 0000000000..dcac009bf4
--- /dev/null
+++ b/modules/payment/src/Event/PaymentEvent.php
@@ -0,0 +1,42 @@
+payment = $payment;
+ }
+
+ /**
+ * Gets the payment.
+ *
+ * @return \Drupal\commerce_payment\Entity\PaymentInterface
+ * The payment.
+ */
+ public function getPayment() {
+ return $this->payment;
+ }
+
+}
diff --git a/modules/payment/src/Event/PaymentEvents.php b/modules/payment/src/Event/PaymentEvents.php
index 9a4abfeab3..eb73d3988e 100644
--- a/modules/payment/src/Event/PaymentEvents.php
+++ b/modules/payment/src/Event/PaymentEvents.php
@@ -10,7 +10,74 @@ final class PaymentEvents {
* @Event
*
* @see \Drupal\commerce_payment\Event\FilterPaymentGatewaysEvent
+ *
+ * @var string
*/
const FILTER_PAYMENT_GATEWAYS = 'commerce_payment.filter_payment_gateways';
+
+ /**
+ * Name of the event fired after loading a payment.
+ *
+ * @Event
+ *
+ * @see \Drupal\commerce_payment\Event\PaymentEvent
+ *
+ * @var string
+ */
+ const PAYMENT_LOAD = 'commerce_payment.commerce_payment.load';
+
+ /**
+ * Name of the event fired before saving a payment.
+ *
+ * @Event
+ *
+ * @see \Drupal\commerce_payment\Event\PaymentEvent
+ *
+ * @var string
+ */
+ const PAYMENT_PRESAVE = 'commerce_payment.commerce_payment.presave';
+
+ /**
+ * Name of the event fired after saving a new payment.
+ *
+ * @Event
+ *
+ * @see \Drupal\commerce_payment\Event\PaymentEvent
+ */
+ const PAYMENT_INSERT = 'commerce_payment.commerce_payment.insert';
+
+ /**
+ * Name of the event fired after saving an existing payment.
+ *
+ * @Event
+ *
+ * @see \Drupal\commerce_payment\Event\PaymentEvent
+ *
+ * @var string
+ */
+ const PAYMENT_UPDATE = 'commerce_payment.commerce_payment.update';
+
+ /**
+ * Name of the event fired before deleting a payment.
+ *
+ * @Event
+ *
+ * @see \Drupal\commerce_payment\Event\PaymentEvent
+ *
+ * @var string
+ */
+ const PAYMENT_PREDELETE = 'commerce_payment.commerce_payment.predelete';
+
+ /**
+ * Name of the event fired after deleting a payment.
+ *
+ * @Event
+ *
+ * @see \Drupal\commerce_payment\Event\PaymentEvent
+ *
+ * @var string
+ */
+ const PAYMENT_DELETE = 'commerce_payment.commerce_payment.delete';
+
}
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..179ebc0ca6
--- /dev/null
+++ b/modules/payment/tests/modules/payment_events_test/payment_events_test.info.yml
@@ -0,0 +1,4 @@
+name: 'Payment 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..f748494531
--- /dev/null
+++ b/modules/payment/tests/modules/payment_events_test/src/EventSubscriber.php
@@ -0,0 +1,53 @@
+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->getPayment(),
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events[PaymentEvents::PAYMENT_LOAD][] = ['paymentEvent'];
+ $events[PaymentEvents::PAYMENT_INSERT][] = ['paymentEvent'];
+ return $events;
+ }
+
+}
diff --git a/modules/payment/tests/src/Kernel/PaymentEventsTest.php b/modules/payment/tests/src/Kernel/PaymentEventsTest.php
new file mode 100644
index 0000000000..6069fb0336
--- /dev/null
+++ b/modules/payment/tests/src/Kernel/PaymentEventsTest.php
@@ -0,0 +1,107 @@
+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() {
+ // Create a dummy payment.
+ $payment = Payment::create([
+ 'payment_gateway' => 'example',
+ 'payment_method' => 'credit_card',
+ 'remote_id' => '123456',
+ 'amount' => [
+ 'number' => '39.99',
+ 'currency_code' => 'USD',
+ ],
+ 'state' => 'capture_completed',
+ 'test' => TRUE,
+ ]);
+ $payment->save();
+
+ // Check the create event.
+ $event_recorder = \Drupal::state()->get('payment_events_test.event', FALSE);
+ $this->assertEquals('commerce_payment.commerce_payment.insert', $event_recorder['event_name']);
+ $this->assertEquals($payment->id(), $event_recorder['event_entity']->id());
+
+ // Reload the payment.
+ $this->reloadEntity($payment);
+
+ // Check the load event.
+ $event_recorder = \Drupal::state()->get('payment_events_test.event', FALSE);
+ $this->assertEquals('commerce_payment.commerce_payment.load', $event_recorder['event_name']);
+ }
+
+}