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']); + } + +}