From a3933aa864e56694a789e259e4562475436d270e Mon Sep 17 00:00:00 2001 From: florenttorregrosa Date: Wed, 12 Sep 2018 17:28:45 +0200 Subject: [PATCH] Issue #2943888 by pmagunia, Grimreaper, joachim: ability to re-purchase a license to extend it --- commerce_license.module | 21 +- commerce_license.services.yml | 7 + commerce_license.workflows.yml | 14 +- config/schema/commerce_license.schema.yml | 9 + src/Entity/License.php | 45 ++- src/Entity/LicenseInterface.php | 8 + src/EventSubscriber/LicenseOrderSyncSubscriber.php | 37 ++- .../LicenseRenewalCartEventSubscriber.php | 86 ++++++ src/FormAlter/ProductVariationTypeFormAlter.php | 41 +++ src/LicenseAvailabilityCheckerExistingRights.php | 17 +- src/LicenseStorage.php | 19 ++ src/LicenseStorageInterface.php | 14 + .../Commerce/LicenseType/RenewableLicenseType.php | 30 ++ tests/src/Kernel/CommerceOrderSyncRenewalTest.php | 304 +++++++++++++++++++++ 14 files changed, 633 insertions(+), 19 deletions(-) create mode 100644 src/EventSubscriber/LicenseRenewalCartEventSubscriber.php create mode 100644 tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/RenewableLicenseType.php create mode 100644 tests/src/Kernel/CommerceOrderSyncRenewalTest.php diff --git a/commerce_license.module b/commerce_license.module index 87c5bd6..27a70a3 100644 --- a/commerce_license.module +++ b/commerce_license.module @@ -72,9 +72,26 @@ function commerce_license_commerce_order_item_delete(EntityInterface $entity) { return; } - // Delete the license. + /** @var \Drupal\commerce_license\Entity\LicenseInterface $license */ $license = $entity->license->entity; - $license->delete(); + + // In case of renewal, set the license back to active. + if ($license->getState()->value == 'renewal_in_progress') { + // We need the intermediate state of renewal_cancelled because otherwise in + // the License entity preSave method we would continue to extend the + // expiration of the license. + $transition = $license->getState()->getWorkflow()->getTransition('cancel_renewal'); + $license->getState()->applyTransition($transition); + $license->save(); + + $transition = $license->getState()->getWorkflow()->getTransition('confirm'); + $license->getState()->applyTransition($transition); + $license->save(); + } + else { + // Delete the license. + $license->delete(); + } } /** diff --git a/commerce_license.services.yml b/commerce_license.services.yml index f80f667..21a2c8d 100644 --- a/commerce_license.services.yml +++ b/commerce_license.services.yml @@ -21,6 +21,13 @@ services: tags: - { name: commerce_order.order_processor } + commerce_license.license_renewal_cart_event_subscriber: + class: Drupal\commerce_license\EventSubscriber\LicenseRenewalCartEventSubscriber + arguments: + - '@entity_type.manager' + tags: + - { name: event_subscriber } + commerce_license.license_multiples_cart_event_subscriber: class: Drupal\commerce_license\EventSubscriber\LicenseMultiplesCartEventSubscriber tags: diff --git a/commerce_license.workflows.yml b/commerce_license.workflows.yml index 1e1ddf4..4a56eb1 100644 --- a/commerce_license.workflows.yml +++ b/commerce_license.workflows.yml @@ -11,6 +11,10 @@ license_default: label: Pending active: label: Active + renewal_in_progress: + label: Renewal in progress + renewal_cancelled: + label: Renewal cancelled suspended: label: Suspended expired: @@ -31,8 +35,16 @@ license_default: to: pending confirm: label: 'Confirm Activation' - from: [new, pending] + from: [new, pending, renewal_in_progress, renewal_cancelled] to: active + renewal_in_progress: + label: 'Renewal in progress' + from: [active] + to: renewal_in_progress + cancel_renewal: + label: 'Cancel renewal' + from: [renewal_in_progress] + to: renewal_cancelled suspend: label: 'Suspend' from: [active] diff --git a/config/schema/commerce_license.schema.yml b/config/schema/commerce_license.schema.yml index 7e5ec7d..c36a61b 100644 --- a/config/schema/commerce_license.schema.yml +++ b/config/schema/commerce_license.schema.yml @@ -8,6 +8,15 @@ commerce_product.commerce_product_variation_type.*.third_party.commerce_license: activate_on_place: type: boolean label: 'Whether to activate a license when the order is placed' + allow_renewal: + type: boolean + label: 'Allow renewal of license before expiration' + interval: + type: text + label: 'Allow renewal of license within this timeframe of expiration (multiplier)' + period: + type: text + label: 'Allow renewal of license within this timeframe of expiration (period unit)' views.field.commerce_license__entity_label: type: views.field.entity_label diff --git a/src/Entity/License.php b/src/Entity/License.php index f59e7eb..bcffc4e 100644 --- a/src/Entity/License.php +++ b/src/Entity/License.php @@ -2,6 +2,7 @@ namespace Drupal\commerce_license\Entity; +use Drupal\commerce_product\Entity\ProductVariationType; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Entity\ContentEntityBase; @@ -110,6 +111,11 @@ class License extends ContentEntityBase implements LicenseInterface { $this->setRenewedTime($activation_time); } + // Renewal completed. + if (isset($this->original) && $this->original->state->value == 'renewal_in_progress') { + $this->setExpiresTime($this->calculateExpirationTime($this->getExpiresTime())); + } + // Set the expiry time on a new license, but allow licenses to be // created with a set expiry, such as in the case of a migration. if (!$this->getExpiresTime()) { @@ -282,7 +288,6 @@ class License extends ContentEntityBase implements LicenseInterface { return $this; } - /** * {@inheritdoc} */ @@ -476,4 +481,42 @@ class License extends ContentEntityBase implements LicenseInterface { return [\Drupal::currentUser()->id()]; } + /** + * {@inheritdoc} + */ + public function canRenew() { + if ($this->state->value != 'active') { + return FALSE; + } + + $variation = $this->getPurchasedEntity(); + $product_variation_type_id = $variation->bundle(); + $product_variation_type = ProductVariationType::load($product_variation_type_id); + + $allow_renewal = $product_variation_type->getThirdPartySetting('commerce_license', 'allow_renewal', FALSE); + if (!$allow_renewal) { + return FALSE; + } + + $allow_renewal_window_interval = $product_variation_type->getThirdPartySetting('commerce_license', 'interval'); + $allow_renewal_window_period = $product_variation_type->getThirdPartySetting('commerce_license', 'period'); + + // Code from Drupal\recurring_period\Plugin\RecurringPeriod\RollingInterval + // method calculateDate. + // Create a DateInterval that represents the interval. + // TODO: This can be removed when https://www.drupal.org/node/2900435 lands. + $interval_plugin_definition = \Drupal::service('plugin.manager.interval.intervals')->getDefinition($allow_renewal_window_period); + $value = $allow_renewal_window_interval * $interval_plugin_definition['multiplier']; + $date_interval = \DateInterval::createFromDateString($value . ' ' . $interval_plugin_definition['php']); + $renewal_window_start_time = (new \DateTime(date('r', $this->getExpiresTime()))) + ->setTimezone(new \DateTimeZone(commerce_licence_get_user_timezone($this->getOwner()))); + $renewal_window_start_time->sub($date_interval); + if ($renewal_window_start_time->getTimestamp() < \Drupal::time()->getRequestTime()) { + return TRUE; + } + else { + return FALSE; + } + } + } diff --git a/src/Entity/LicenseInterface.php b/src/Entity/LicenseInterface.php index 6d354da..5ef2136 100644 --- a/src/Entity/LicenseInterface.php +++ b/src/Entity/LicenseInterface.php @@ -151,4 +151,12 @@ interface LicenseInterface extends EntityChangedInterface, EntityOwnerInterface */ public static function getWorkflowId(LicenseInterface $license); + /** + * Checks if the license can be renewed at this time. + * + * @return bool + * TRUE if the license can be renewed. FALSE otherwise. + */ + public function canRenew(); + } diff --git a/src/EventSubscriber/LicenseOrderSyncSubscriber.php b/src/EventSubscriber/LicenseOrderSyncSubscriber.php index bcf0f73..3d6f56a 100644 --- a/src/EventSubscriber/LicenseOrderSyncSubscriber.php +++ b/src/EventSubscriber/LicenseOrderSyncSubscriber.php @@ -23,7 +23,7 @@ class LicenseOrderSyncSubscriber implements EventSubscriberInterface { /** * The license storage. * - * @var \Drupal\Core\Entity\EntityStorageInterface + * @var \Drupal\commerce_license\LicenseStorage */ protected $licenseStorage; @@ -107,6 +107,7 @@ class LicenseOrderSyncSubscriber implements EventSubscriberInterface { if (empty($order_item->license->entity)) { // Create a new license. It will be in the 'new' state and so not yet // active. + /** @var \Drupal\commerce_license\Entity\LicenseInterface $license */ $license = $this->licenseStorage->createFromOrderItem($order_item); $license->save(); @@ -118,14 +119,20 @@ class LicenseOrderSyncSubscriber implements EventSubscriberInterface { } else { // Get the existing license the order item refers to. + /** @var \Drupal\commerce_license\Entity\LicenseInterface $license */ $license = $order_item->license->entity; } - // Now determine whether to activate it. + // Now determine whether to activate or renew it. $activate_license = FALSE; + $renew_license = FALSE; if ($reached_state == 'completed') { // Always activate the license when we reach the 'completed' state. $activate_license = TRUE; + + if ($license->getState()->value == 'renewal_in_progress') { + $renew_license = TRUE; + } } else { // Activate the license in the 'place' transition if the product @@ -146,13 +153,16 @@ class LicenseOrderSyncSubscriber implements EventSubscriberInterface { continue; } - // Attempt to activate and confirm the license. - // TODO: This needs to be expanded for synchronizable licenses. - // TODO: how does a license type plugin indicate that it's not able to - // activate? And how do we notify the order at this point? - $transition = $license->getState()->getWorkflow()->getTransition('activate'); - $license->getState()->applyTransition($transition); - $license->save(); + // We don't want the activation transition when renewing a license. + if (!$renew_license) { + // Attempt to activate and confirm the license. + // TODO: This needs to be expanded for synchronizable licenses. + // TODO: how does a license type plugin indicate that it's not able to + // activate? And how do we notify the order at this point? + $transition = $license->getState()->getWorkflow()->getTransition('activate'); + $license->getState()->applyTransition($transition); + $license->save(); + } $transition = $license->getState()->getWorkflow()->getTransition('confirm'); $license->getState()->applyTransition($transition); @@ -174,8 +184,17 @@ class LicenseOrderSyncSubscriber implements EventSubscriberInterface { foreach ($license_order_items as $order_item) { // Get the license from the order item. + /** @var \Drupal\commerce_license\Entity\LicenseInterface $license */ $license = $order_item->license->entity; + // License is in renewal process. Back to active. + if ($license->getState()->value == 'renewal_in_progress') { + $transition = $license->getState()->getWorkflow()->getTransition('confirm'); + $license->getState()->applyTransition($transition); + $license->save(); + continue; + } + // Cancel the license. $transition = $license->getState()->getWorkflow()->getTransition('cancel'); $license->getState()->applyTransition($transition); diff --git a/src/EventSubscriber/LicenseRenewalCartEventSubscriber.php b/src/EventSubscriber/LicenseRenewalCartEventSubscriber.php new file mode 100644 index 0000000..e913109 --- /dev/null +++ b/src/EventSubscriber/LicenseRenewalCartEventSubscriber.php @@ -0,0 +1,86 @@ +licenseStorage = $entity_type_manager->getStorage('commerce_license'); + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = [ + CartEvents::CART_ENTITY_ADD => ['onCartEntityAdd', 100], + ]; + return $events; + } + + /** + * Sets the already existing license in the order item. + * + * @param \Drupal\commerce_cart\Event\CartEntityAddEvent $event + * The cart event. + */ + public function onCartEntityAdd(CartEntityAddEvent $event) { + $order_item = $event->getOrderItem(); + + // Only act if the order item has a license reference field. + if (!$order_item->hasField('license')) { + return; + } + + // We can't renew license types that don't allow us to find a license + // given only a product variation and a user. + $variation = $order_item->getPurchasedEntity(); + $license_type_plugin = $variation->license_type->first()->getTargetInstance(); + if (!($license_type_plugin instanceof ExistingRightsFromConfigurationCheckingInterface)) { + return; + } + + $existing_license = $this->licenseStorage->getExistingLicense($variation, $order_item->getOrder()->getCustomerId()); + if ($existing_license && $existing_license->canRenew()) { + $order_item->license = $existing_license->id(); + $order_item->save(); + + $transition = $existing_license->getState()->getWorkflow()->getTransition('renewal_in_progress'); + $existing_license->getState()->applyTransition($transition); + $existing_license->save(); + } + } + +} diff --git a/src/FormAlter/ProductVariationTypeFormAlter.php b/src/FormAlter/ProductVariationTypeFormAlter.php index 3948ace..173350d 100644 --- a/src/FormAlter/ProductVariationTypeFormAlter.php +++ b/src/FormAlter/ProductVariationTypeFormAlter.php @@ -83,6 +83,38 @@ class ProductVariationTypeFormAlter { '#default_value' => $this->variation_type->getThirdPartySetting('commerce_license', 'activate_on_place', FALSE), ]; + $our_form['license']['allow_renewal'] = [ + '#type' => 'checkbox', + '#title' => t("Allow renewal before expiration"), + '#description' => t( + "Allows a customer to renew their license by re-purchasing the product for it." + ), + '#default_value' => $this->variation_type->getThirdPartySetting('commerce_license', 'allow_renewal', FALSE), + ]; + + $our_form['license']['allow_renewal_window'] = [ + '#type' => 'details', + '#title' => t("Allow renewal window"), + '#open' => TRUE, + '#states' => [ + 'visible' => [ + 'input#edit-allow-renewal' => ['checked' => TRUE], + ], + ], + ]; + + $our_form['license']['allow_renewal_window']['interval'] = [ + '#type' => 'interval', + '#title' => t("Allow renewal window"), + '#description' => t( + "The interval before the license's expiry during which re-purchase is allowed. Prior to this interval, re-purchase is blocked, as normal." + ), + '#default_value' => [ + 'interval' => $this->variation_type->getThirdPartySetting('commerce_license', 'interval'), + 'period' => $this->variation_type->getThirdPartySetting('commerce_license', 'period'), + ], + ]; + // Insert our form elements into the form after the 'traits' element. // The form elements don't have their weight set, so we can't use that. $traits_element_form_array_index = array_search('traits', array_keys($form)); @@ -174,6 +206,15 @@ class ProductVariationTypeFormAlter { $activate_on_place = $form_state->getValue('activate_on_place'); $variation_type->setThirdPartySetting('commerce_license', 'activate_on_place', $activate_on_place); + $allow_renewal = $form_state->getValue('allow_renewal'); + $variation_type->setThirdPartySetting('commerce_license', 'allow_renewal', $allow_renewal); + + $interval = $form_state->getValue('interval'); + $variation_type->setThirdPartySetting('commerce_license', 'interval', $interval); + + $period = $form_state->getValue('period'); + $variation_type->setThirdPartySetting('commerce_license', 'period', $period); + // This is saving it a second time... but Commerce does the same in its form // alterations. $variation_type->save(); diff --git a/src/LicenseAvailabilityCheckerExistingRights.php b/src/LicenseAvailabilityCheckerExistingRights.php index be2feab..29b03a6 100644 --- a/src/LicenseAvailabilityCheckerExistingRights.php +++ b/src/LicenseAvailabilityCheckerExistingRights.php @@ -8,7 +8,6 @@ use Drupal\commerce\AvailabilityCheckerInterface; use Drupal\commerce\Context; use Drupal\commerce\PurchasableEntityInterface; use Drupal\commerce_recurring\RecurringOrderManager; -use Drupal\commerce_product\Entity\ProductVariationInterface; use Drupal\commerce_license\Plugin\Commerce\LicenseType\ExistingRightsFromConfigurationCheckingInterface; /** @@ -97,15 +96,21 @@ class LicenseAvailabilityCheckerExistingRights implements AvailabilityCheckerInt } } + $customer = $context->getCustomer(); + // Load the full user entity for the plugin. + $user = $this->entityTypeManager->getStorage('user')->load($customer->id()); + + // Handle licence renewal. + /** @var \Drupal\commerce_license\Entity\LicenseInterface $existing_license */ + $existing_license = $this->entityTypeManager->getStorage('commerce_license')->getExistingLicense($entity, $user->id()); + if ($existing_license && $existing_license->canRenew()) { + return; + } + // Hand over to the license type plugin configured in the product variation, // to let it determine whether the user already has what the license would // grant. - $customer = $context->getCustomer(); - $license_type_plugin = $entity->license_type->first()->getTargetInstance(); - - // Load the full user entity for the plugin. - $user = $this->entityTypeManager->getStorage('user')->load($customer->id()); $existing_rights_result = $license_type_plugin->checkUserHasExistingRights($user); if ($existing_rights_result->hasExistingRights()) { diff --git a/src/LicenseStorage.php b/src/LicenseStorage.php index 87a8778..6e3ae5c 100644 --- a/src/LicenseStorage.php +++ b/src/LicenseStorage.php @@ -58,4 +58,23 @@ class LicenseStorage extends CommerceContentEntityStorage implements LicenseStor return $license; } + /** + * {@inheritdoc} + */ + public function getExistingLicense(ProductVariationInterface $variation, $uid) { + $existing_licenses_ids = $this->getQuery() + ->condition('state', 'active') + ->condition('uid', $uid) + ->condition('product_variation', $variation->id()) + ->execute(); + + if (!empty($existing_licenses_ids)) { + $existing_license_id = array_shift($existing_licenses_ids); + return $this->load($existing_license_id); + } + else { + return FALSE; + } + } + } diff --git a/src/LicenseStorageInterface.php b/src/LicenseStorageInterface.php index 88def38..1b7d8f5 100644 --- a/src/LicenseStorageInterface.php +++ b/src/LicenseStorageInterface.php @@ -8,6 +8,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\commerce_order\Entity\OrderItemInterface; use Drupal\commerce_license\Entity\LicenseInterface; use Drupal\commerce_product\Entity\ProductVariationInterface; +use Drupal\user\UserInterface; /** * Defines the storage handler class for License entities. @@ -46,4 +47,17 @@ interface LicenseStorageInterface extends ContentEntityStorageInterface { */ public function createFromProductVariation(ProductVariationInterface $variation, $uid); + /** + * Get existing active license given a product variation and a user ID. + * + * @param \Drupal\commerce_product\Entity\ProductVariationInterface $variation + * The product variation. + * @param int $uid + * The uid for whom the license will be retrieved. + * + * @return \Drupal\commerce_license\Entity\LicenseInterface|false + * An existing license entity. FALSE otherwise. + */ + public function getExistingLicense(ProductVariationInterface $variation, $uid); + } diff --git a/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/RenewableLicenseType.php b/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/RenewableLicenseType.php new file mode 100644 index 0000000..43eecaf --- /dev/null +++ b/tests/modules/commerce_license_test/src/Plugin/Commerce/LicenseType/RenewableLicenseType.php @@ -0,0 +1,30 @@ +t("You already have the rights."), + $this->t("The user already has the rights.") + ); + } + +} diff --git a/tests/src/Kernel/CommerceOrderSyncRenewalTest.php b/tests/src/Kernel/CommerceOrderSyncRenewalTest.php new file mode 100644 index 0000000..a1fe3a3 --- /dev/null +++ b/tests/src/Kernel/CommerceOrderSyncRenewalTest.php @@ -0,0 +1,304 @@ +installEntitySchema('profile'); + $this->installEntitySchema('commerce_product'); + $this->installEntitySchema('commerce_product_variation'); + $this->installEntitySchema('commerce_order'); + $this->installEntitySchema('commerce_order_item'); + $this->installEntitySchema('commerce_license'); + $this->installConfig('commerce_order'); + $this->installConfig('commerce_product'); + $this->createUser(); + + $this->licenseStorage = $this->container->get('entity_type.manager')->getStorage('commerce_license'); + + // Create an order type for licenses which uses the fulfillment workflow. + $this->orderType = $this->createEntity('commerce_order_type', [ + 'id' => 'license_order_type', + 'label' => $this->randomMachineName(), + 'workflow' => 'order_default', + ]); + commerce_order_add_order_items_field($this->orderType); + + // Create an order item type that uses that order type. + $order_item_type = $this->createEntity('commerce_order_item_type', [ + 'id' => 'license_order_item_type', + 'label' => $this->randomMachineName(), + 'purchasableEntityType' => 'commerce_product_variation', + 'orderType' => 'license_order_type', + 'traits' => ['commerce_license_order_item_type'], + ]); + $this->traitManager = \Drupal::service('plugin.manager.commerce_entity_trait'); + $trait = $this->traitManager->createInstance('commerce_license_order_item_type'); + $this->traitManager->installTrait($trait, 'commerce_order_item', $order_item_type->id()); + + // Create a product variation type with the license trait, using our order + // item type. + $this->variationType = $this->createEntity('commerce_product_variation_type', [ + 'id' => 'license_pv_type', + 'label' => $this->randomMachineName(), + 'orderItemType' => 'license_order_item_type', + 'traits' => ['commerce_license'], + ]); + $trait = $this->traitManager->createInstance('commerce_license'); + $this->traitManager->installTrait($trait, 'commerce_product_variation', $this->variationType->id()); + + $this->variationType->setThirdPartySetting('commerce_license', 'allow_renewal', TRUE); + $this->variationType->setThirdPartySetting('commerce_license', 'interval', '1'); + $this->variationType->setThirdPartySetting('commerce_license', 'period', 'month'); + $this->variationType->save(); + + // Create a product variation which grants a license. + $this->variation = $this->createEntity('commerce_product_variation', [ + 'type' => 'license_pv_type', + 'sku' => $this->randomMachineName(), + 'price' => [ + 'number' => 999, + 'currency_code' => 'USD', + ], + 'license_type' => [ + 'target_plugin_id' => 'renewable', + 'target_plugin_configuration' => [], + ], + // Use the rolling interval expiry plugin as it's simple. + 'license_expiration' => [ + 'target_plugin_id' => 'rolling_interval', + 'target_plugin_configuration' => [ + 'interval' => [ + 'interval' => '1', + 'period' => 'year', + ], + ], + ], + ]); + + // We need a product too otherwise tests complain about the missing + // backreference. + $this->createEntity('commerce_product', [ + 'type' => 'default', + 'title' => $this->randomMachineName(), + 'stores' => [$this->store], + 'variations' => [$this->variation], + ]); + $this->reloadEntity($this->variation); + $this->variation->save(); + + // Create a user to use for orders. + $this->user = $this->createUser(); + + $this->installCommerceCart(); + $this->store = $this->createStore(); + + // Create a license in the 'active. + $this->license = $this->createEntity('commerce_license', [ + 'type' => 'renewable', + 'state' => 'active', + 'product_variation' => $this->variation->id(), + 'uid' => $this->user->id(), + // 06/01/2015 @ 1:00pm (UTC). + 'expires' => '1433163600', + 'expiration_type' => [ + 'target_plugin_id' => 'rolling_interval', + 'target_plugin_configuration' => [ + 'interval' => [ + 'interval' => '1', + 'period' => 'year', + ], + ], + ], + ]); + } + + /** + * Tests that a license can't be purchased outside the renewable window. + */ + public function testRenewOutsideRenewalWindow() { + // Mock the current time service. + $expiration_time_outside_window = strtotime('- 2 months', $this->license->getExpiresTime()); + + $mock_builder = $this->getMockBuilder('Drupal\Component\Datetime\TimeInterface') + ->disableOriginalConstructor(); + + $datetime_service = $mock_builder->getMock(); + $datetime_service->expects($this->atLeastOnce()) + ->method('getRequestTime') + ->willReturn($expiration_time_outside_window); + $this->container->set('datetime.time', $datetime_service); + + // Add a product with license to the cart. + $cart_order = $this->container->get('commerce_cart.cart_provider')->createCart('license_order_type', $this->store, $this->user); + $this->cartManager = $this->container->get('commerce_cart.cart_manager'); + $order_item = $this->cartManager->addEntity($cart_order, $this->variation); + + // Assert the order item is NOT in the cart. + $this->assertFalse($cart_order->hasItem($order_item)); + } + + /** + * Tests that a license is extended when you repurchased it. + */ + public function testRenewInRenewalWindow() { + // Mock the current time service. + $expiration_time_inside_window = strtotime('- 1 week', $this->license->getExpiresTime()); + + $mock_builder = $this->getMockBuilder('Drupal\Component\Datetime\TimeInterface') + ->disableOriginalConstructor(); + + $datetime_service = $mock_builder->getMock(); + $datetime_service->expects($this->atLeastOnce()) + ->method('getRequestTime') + ->willReturn($expiration_time_inside_window); + $this->container->set('datetime.time', $datetime_service); + + // Add a product with license to the cart. + $cart_order = $this->container->get('commerce_cart.cart_provider')->createCart('license_order_type', $this->store, $this->user); + $this->cartManager = $this->container->get('commerce_cart.cart_manager'); + $order_item = $this->cartManager->addEntity($cart_order, $this->variation); +// $order_item = $this->reloadEntity($order_item); + + // Check that the order item has the previous license. + $this->assertNotNull($order_item->license->entity, 'The order item has a license set on it.'); + $this->assertEquals($this->license->id(), $order_item->license->entity->id(), 'The order item has a reference to the existing license.'); + + // Assert the order item IS IN the cart. + $this->assertTrue($cart_order->hasItem($order_item)); + + // Take the order through checkout. + $this->completeLicenseOrderCheckout($cart_order); +// $order = $this->reloadEntity($cart_order); + + $this->assertEqual(date(DATE_ATOM, $this->license->getExpiresTime()), date(DATE_ATOM, strtotime('+1 year', 1433163600)), 'The license has been extended for a year.'); + } + + /** + * Tests that a license is active after removing renewing product from cart. + */ + public function testRemovingProductFromCart() { + $this->assertTrue(TRUE); + } + + /** + * Tests that a non renewable license can't be purchased if still active. + */ + public function testNonRenewableLicense() { + $this->assertTrue(TRUE); + } + + /** + * Creates and saves a new entity. + * + * @param string $entity_type + * The entity type to be created. + * @param array $values + * An array of settings. + * Example: 'id' => 'foo'. + * + * @return \Drupal\Core\Entity\EntityInterface + * A new, saved entity. + */ + protected function createEntity($entity_type, array $values) { + /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ + $storage = \Drupal::service('entity_type.manager')->getStorage($entity_type); + $entity = $storage->create($values); + $status = $entity->save(); + $this->assertEquals(SAVED_NEW, $status, new FormattableMarkup('Created %label entity %type.', [ + '%label' => $entity->getEntityType()->getLabel(), + '%type' => $entity->id(), + ])); + // The newly saved entity isn't identical to a loaded one, and would fail + // comparisons. + $entity = $storage->load($entity->id()); + + return $entity; + } + +} -- 1.9.1