Skip to content
Snippets Groups Projects
Commit 54c7d5f3 authored by Dmytrii Kaiun's avatar Dmytrii Kaiun Committed by Jonathan Sacksick
Browse files

Issue #2845321 by lisastreeter, tBKoT, AndyF, jonathanshaw, rszrama,...

Issue #2845321 by lisastreeter, tBKoT, AndyF, jonathanshaw, rszrama, jsacksick, mglaman, maciej.zgadzaj, bojanz, heddn, rinasek, mikcat: Add payment logging to orders.
parent 0a2e6b2e
No related branches found
No related tags found
6 merge requests!379Issue #3491248 Validation is breaking the amount number format decimal point,!375Issue #3413020 by czigor: Using a translatable string as a category for field...,!357Issue #2914933: Add service tags to order-related interfaces,!344Resolve #3107602 "Product attributes do not update visually when switching variations",!343Resolve #3107602 "Product attributes do not update visually when switching variations",!342Issue #3476581 by josephr5000: add OrderItemLabelEvent
......@@ -5,3 +5,7 @@ commerce_cart:
commerce_order:
label: Order
entity_type: commerce_order
commerce_payment:
label: Payment
entity_type: commerce_order
......@@ -64,3 +64,23 @@ commerce_order_admin_comment:
category: commerce_order
label: 'Admin comment'
template: '<p><strong>Admin comment:</strong><br /> {{ comment }}</p>'
payment_added:
category: commerce_payment
label: 'Payment added'
template: '<p>Payment added{% if method %} using {{ method }}{% endif %} with {{ gateway }}: {{ amount|commerce_price_format }}. State: {{ state }}.{% if remote_id %}<br />Transaction ID: {{ remote_id }}{% else %}ID: {{ id }}{% endif %}.</p>'
payment_authorized:
category: commerce_payment
label: 'Payment authorized'
template: '<p>Payment authorized via {{ gateway }} for {{ amount|commerce_price_format }}{% if method %} using {{ method }}{% endif %}.{% if remote_id %}<br />Transaction ID: {{ remote_id }}{% endif %}.</p>'
payment_completed:
category: commerce_payment
label: 'Payment authorized'
template: '<p>Payment captured via {{ gateway }} for {{ amount|commerce_price_format }}{% if method %} using {{ method }}{% endif %}.{% if remote_id %}<br />Transaction ID: {{ remote_id }}{% endif %}.</p>'
payment_updated:
category: commerce_payment
label: 'Payment updated'
template: '<p>Payment updated. Payment balance: {{ amount|commerce_price_format }}. State: {{ state }}.{% if remote_id %}<br />Transaction ID: {{ remote_id }}{% else %}ID: {{ id }}{% endif %}.</p>'
payment_deleted:
category: commerce_payment
label: 'Payment deleted'
template: '<p>Payment deleted: {{ amount|commerce_price_format }}.{% if method %} [{{ method }}].{% endif %}{% if remote_id %}<br />Transaction ID: {{ remote_id }}{% else %}ID: {{ id }}{% endif %}.</p>'
......@@ -39,6 +39,11 @@ class CommerceLogServiceProvider extends ServiceProviderBase {
->addArgument(new Reference('entity_type.manager'))
->addArgument(new Reference('plugin.manager.commerce_log_template'));
}
if (isset($modules['commerce_payment'])) {
$container->register('commerce_log.payment_subscriber', 'Drupal\commerce_log\EventSubscriber\PaymentEventSubscriber')
->addTag('event_subscriber')
->addArgument(new Reference('entity_type.manager'));
}
}
}
<?php
namespace Drupal\commerce_log\EventSubscriber;
use Drupal\commerce_log\LogStorageInterface;
use Drupal\commerce_payment\Event\PaymentEvent;
use Drupal\commerce_payment\Event\PaymentEvents;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscribes to the payment events.
*/
class PaymentEventSubscriber implements EventSubscriberInterface {
/**
* The log storage.
*/
protected LogStorageInterface $logStorage;
/**
* Constructs a new PaymentEventSubscriber object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->logStorage = $entity_type_manager->getStorage('commerce_log');
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
PaymentEvents::PAYMENT_INSERT => ['onPaymentInsert', -100],
PaymentEvents::PAYMENT_UPDATE => ['onPaymentUpdate', -100],
PaymentEvents::PAYMENT_DELETE => ['onPaymentDelete', -100],
];
}
/**
* Creates a log when a payment is added.
*
* @param \Drupal\commerce_payment\Event\PaymentEvent $event
* The payment event.
*/
public function onPaymentInsert(PaymentEvent $event): void {
$payment = $event->getPayment();
// Skip for payments without order.
if ($payment->getOrder() === NULL) {
return;
}
$state = $payment->getState();
// Set template based on payment state.
$template_id = match ($state->getId()) {
'authorized' => 'payment_authorized',
'completed' => 'payment_completed',
default => 'payment_added',
};
$this->logStorage->generate($payment->getOrder(), $template_id, [
'id' => $payment->id(),
'remote_id' => $payment->getRemoteId(),
'amount' => $payment->getAmount(),
'state' => $state->getLabel(),
'method' => $payment->getPaymentMethod()?->label(),
'gateway' => $payment->getPaymentGateway()?->label(),
])->save();
}
/**
* Creates a log when a payment is updated.
*
* @param \Drupal\commerce_payment\Event\PaymentEvent $event
* The payment event.
*/
public function onPaymentUpdate(PaymentEvent $event): void {
$payment = $event->getPayment();
// Skip for payments without order.
if ($payment->getOrder() === NULL) {
return;
}
$state = $payment->getState();
$template_id = 'payment_updated';
// Gets original state if possible.
$original_state = isset($payment->original) ? $payment->original->getState()
->getId() : '';
// For changed state to the authorized use another template.
if ($state->getId() === 'authorized' && $original_state !== 'authorized') {
$template_id = 'payment_authorized';
}
// For changed state to the completed use another template.
if ($state->getId() === 'completed' && $original_state !== 'completed') {
$template_id = 'payment_completed';
}
$this->logStorage->generate($payment->getOrder(), $template_id, [
'id' => $payment->id(),
'remote_id' => $payment->getRemoteId(),
'amount' => $payment->getBalance(),
'state' => $state->getLabel(),
'method' => $payment->getPaymentMethod()?->label(),
'gateway' => $payment->getPaymentGateway()?->label(),
])->save();
}
/**
* Creates a log when a payment is deleted.
*
* @param \Drupal\commerce_payment\Event\PaymentEvent $event
* The payment event.
*/
public function onPaymentDelete(PaymentEvent $event): void {
$payment = $event->getPayment();
// Skip for payments without order.
if ($payment->getOrder() === NULL) {
return;
}
$this->logStorage->generate($payment->getOrder(), 'payment_deleted', [
'id' => $payment->id(),
'remote_id' => $payment->getRemoteId(),
'amount' => $payment->getBalance(),
'method' => $payment->getPaymentMethod()?->label(),
])->save();
}
}
<?php
namespace Drupal\Tests\commerce_log\Kernel;
use Drupal\commerce_log\LogStorageInterface;
use Drupal\commerce_log\LogViewBuilder;
use Drupal\commerce_order\Entity\Order;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_payment\Entity\Payment;
use Drupal\commerce_payment\Entity\PaymentGateway;
use Drupal\commerce_payment\Entity\PaymentMethod;
use Drupal\commerce_price\Price;
use Drupal\commerce_product\Entity\Product;
use Drupal\commerce_product\Entity\ProductVariation;
use Drupal\profile\Entity\Profile;
use Drupal\Tests\commerce_order\Kernel\OrderKernelTestBase;
/**
* Tests integration with payment events.
*
* @group commerce
*/
class PaymentIntegrationTest extends OrderKernelTestBase {
/**
* A sample order.
*/
protected OrderInterface $order;
/**
* The log storage.
*/
protected LogStorageInterface $logStorage;
/**
* The log view builder.
*/
protected LogViewBuilder $logViewBuilder;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'address',
'commerce_product',
'commerce_payment',
'commerce_payment_example',
'commerce_log',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('commerce_log');
$this->installEntitySchema('commerce_payment');
$this->installEntitySchema('commerce_payment_method');
$this->installConfig('commerce_payment');
$this->logStorage = $this->container->get('entity_type.manager')
->getStorage('commerce_log');
$this->logViewBuilder = $this->container->get('entity_type.manager')
->getViewBuilder('commerce_log');
$payment_gateway = PaymentGateway::create([
'id' => 'example',
'label' => 'Example',
'plugin' => 'example_onsite',
]);
$payment_gateway->save();
$user = $this->createUser(['mail' => $this->randomString() . '@example.com']);
$profile = Profile::create([
'type' => 'customer',
]);
$profile->save();
$profile = $this->reloadEntity($profile);
$payment_method_active = PaymentMethod::create([
'uid' => $user->id(),
'type' => 'credit_card',
'payment_gateway' => 'example',
'card_type' => 'visa',
'card_number' => '1111',
'billing_profile' => $profile,
'reusable' => TRUE,
]);
$payment_method_active->save();
$product = Product::create([
'type' => 'default',
'title' => 'Default testing product',
]);
$product->save();
$variation1 = ProductVariation::create([
'type' => 'default',
'sku' => 'TEST_' . strtolower($this->randomMachineName()),
'title' => $this->randomString(),
'status' => 1,
'price' => new Price('12.00', 'USD'),
]);
$variation1->save();
$product->addVariation($variation1)->save();
/** @var \Drupal\commerce_order\OrderItemStorageInterface $order_item_storage */
$order_item_storage = $this->container->get('entity_type.manager')
->getStorage('commerce_order_item');
$order_item1 = $order_item_storage->createFromPurchasableEntity($variation1);
$order_item1->save();
$order = Order::create([
'type' => 'default',
'store_id' => $this->store->id(),
'state' => 'draft',
'mail' => $user->getEmail(),
'uid' => $user->id(),
'ip_address' => '127.0.0.1',
'order_number' => '6',
'billing_profile' => $profile,
'order_items' => [$order_item1],
]);
$order->save();
$this->order = $this->reloadEntity($order);
}
/**
* Tests that a log is generated on payment insert, update and delete.
*/
public function testPaymentLogs(): void {
// Create a dummy payment.
$payment = Payment::create([
'order_id' => $this->order->id(),
'payment_gateway' => 'example',
'payment_method' => 1,
'remote_id' => '123456',
'amount' => [
'number' => '39.99',
'currency_code' => 'USD',
],
'state' => 'new',
'test' => TRUE,
]);
// Check the payment added log.
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(1, count($logs));
$log = $logs[1];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText("Payment added using Visa ending in 1111 with Example: $39.99. State: New.Transaction ID: 123456.");
// Reload the payment.
$this->reloadEntity($payment);
// Check the payment become authorized log.
$payment->setState('authorized');
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(2, count($logs));
$log = $logs[2];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment authorized via Example for $39.99 using Visa ending in 1111.Transaction ID: 123456.');
// Reload the payment.
$this->reloadEntity($payment);
// Check the payment become completed log.
$payment->setState('completed');
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(3, count($logs));
$log = $logs[3];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment captured via Example for $39.99 using Visa ending in 1111.Transaction ID: 123456.');
// Reload the payment.
$this->reloadEntity($payment);
// Check the payment updated log.
$payment->setRefundedAmount(new Price('10.00', 'USD'));
$payment->setState('partially_refunded');
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(4, count($logs));
$log = $logs[4];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment updated. Payment balance: $29.99. State: Partially refunded.Transaction ID: 123456.');
// Reload the payment.
$this->reloadEntity($payment);
// Check the payment deleted log.
$payment->delete();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(5, count($logs));
$log = $logs[5];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment deleted: $29.99. [Visa ending in 1111].Transaction ID: 123456.');
}
/**
* Tests that a log is generated when an authorized payments is added.
*/
public function testAuthorizedPaymentCreationLogs(): void {
// Create a dummy payment.
$payment = Payment::create([
'order_id' => $this->order->id(),
'payment_gateway' => 'example',
'payment_method' => 1,
'remote_id' => '123456',
'amount' => [
'number' => '39.99',
'currency_code' => 'USD',
],
'state' => 'authorized',
'test' => TRUE,
]);
// Check the payment added log.
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(1, count($logs));
$log = $logs[1];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment authorized via Example for $39.99 using Visa ending in 1111.Transaction ID: 123456.');
}
/**
* Tests that a log is generated when a completed payment is inserted.
*/
public function testCompletedPaymentCreationLogs(): void {
// Create a dummy payment.
$payment = Payment::create([
'order_id' => $this->order->id(),
'payment_gateway' => 'example',
'payment_method' => 1,
'remote_id' => '123456',
'amount' => [
'number' => '39.99',
'currency_code' => 'USD',
],
'state' => 'completed',
'test' => TRUE,
]);
// Check the payment added log.
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(1, count($logs));
$log = $logs[1];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment captured via Example for $39.99 using Visa ending in 1111.Transaction ID: 123456.');
}
/**
* Tests that a log is created when a payment without method is saved.
*/
public function testPaymentWithoutMethodLog(): void {
// Create a dummy payment without payment method.
$payment = Payment::create([
'order_id' => $this->order->id(),
'payment_gateway' => 'example',
'remote_id' => '123456',
'amount' => [
'number' => '39.99',
'currency_code' => 'USD',
],
'state' => 'new',
'test' => TRUE,
]);
// Check that log was added on creation.
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(1, count($logs));
$log = $logs[1];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment added with Example: $39.99. State: New.Transaction ID: 123456.');
// Reload the payment.
$this->reloadEntity($payment);
// Check that log was added on update.
$payment->setState('completed');
$payment->save();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(2, count($logs));
$log = $logs[2];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment captured via Example for $39.99.Transaction ID: 123456.');
// Reload the payment.
$this->reloadEntity($payment);
// Check the payment deleted log.
$payment->delete();
$logs = $this->logStorage->loadMultipleByEntity($this->order);
$this->assertEquals(3, count($logs));
$log = $logs[3];
$build = $this->logViewBuilder->view($log);
$this->render($build);
$this->assertText('Payment deleted: $39.99.Transaction ID: 123456.');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment