Last active
June 27, 2018 20:50
-
-
Save clrockwell/397c20bc270451cd11ccc34c2a53984e to your computer and use it in GitHub Desktop.
Provide a prorated discount for license roles; useful in upsells when someone already has purchased role, they will receive a discount equal to the amount of time left.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Drupal\commerce_promotion_prorated\Plugin\Commerce\PromotionOffer; | |
use Drupal\commerce_license\Entity\License; | |
use Drupal\commerce_order\Adjustment; | |
use Drupal\commerce_order\Entity\Order; | |
use Drupal\commerce_order\Entity\OrderItem; | |
use Drupal\commerce_order\Entity\OrderItemInterface; | |
use Drupal\commerce_price\Price; | |
use Drupal\commerce_product\Entity\ProductAttributeValue; | |
use Drupal\commerce_product\Entity\ProductVariation; | |
use Drupal\commerce_promotion\Entity\PromotionInterface; | |
use Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\PromotionOfferBase; | |
use Drupal\commerce_promotion_prorated\LegacyService; | |
use Drupal\Component\Datetime\TimeInterface; | |
use Drupal\Component\Serialization\Json; | |
use Drupal\Core\Entity\EntityInterface; | |
use Drupal\Core\Entity\EntityTypeManagerInterface; | |
use Drupal\Core\Session\AccountInterface; | |
use Drupal\openid_connect\Authmap; | |
use Drupal\platform_connector\ConnectionService; | |
use Drupal\platform_connector\SubscriptionUtility; | |
use Drupal\scholarrx_commerce_promotions\Plugin\Commerce\Condition\ClientProduct; | |
use GuzzleHttp\Client; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
/** | |
* Provides the prorated percentage for license orders. | |
* | |
* @CommercePromotionOffer( | |
* id = "license_prorated_offer", | |
* label = @Translation("Prorated discount"), | |
* entity_type = "commerce_order_item", | |
* ) | |
*/ | |
class LicenseProrated extends PromotionOfferBase { | |
/** | |
* @var \Drupal\Core\Entity\EntityStorageInterface | |
*/ | |
protected $licenseStorage; | |
/** | |
* @var \Drupal\Core\Entity\EntityStorageInterface | |
*/ | |
protected $orderItemStorage; | |
/** | |
* @var \Drupal\Core\Session\AccountInterface | |
*/ | |
protected $currentUser; | |
/** | |
* Drupal\platform_connector\ConnectionService definition. | |
* | |
* @var \Drupal\platform_connector\ConnectionService | |
*/ | |
protected $connectionService; | |
/** | |
* @var \Drupal\Component\Datetime\TimeInterface | |
*/ | |
protected $time; | |
/** | |
* @var \Drupal\openid_connect\Authmap | |
*/ | |
protected $authmap; | |
/** | |
* @var \Drupal\commerce_promotion_prorated\LegacyService | |
*/ | |
protected $legacyService; | |
public function __construct(array $configuration, $plugin_id, $plugin_definition, \Drupal\commerce_price\RounderInterface $rounder, EntityTypeManagerInterface $entityTypeManager, AccountInterface $user, ConnectionService $connectionService, TimeInterface $time, Authmap $authmap, LegacyService $legacyService) { | |
parent::__construct($configuration, $plugin_id, $plugin_definition, $rounder); | |
$this->licenseStorage = $entityTypeManager->getStorage('commerce_license'); | |
$this->orderItemStorage = $entityTypeManager->getStorage('commerce_order_item'); | |
$this->currentUser = $user; | |
$this->time = $time; | |
$this->connectionService = $connectionService; | |
$this->authmap = $authmap; | |
$this->legacyService = $legacyService; | |
} | |
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | |
return new static( | |
$configuration, | |
$plugin_id, | |
$plugin_definition, | |
$container->get('commerce_price.rounder'), | |
$container->get('entity_type.manager'), | |
$container->get('current_user'), | |
$container->get('platform_connector.connection'), | |
$container->get('datetime.time'), | |
$container->get('openid_connect.authmap'), | |
$container->get('commerce_promotion_prorated.legacy') | |
); | |
} | |
public function apply(EntityInterface $entity, PromotionInterface $promotion) { | |
if ($this->currentUser->isAnonymous()) { | |
return; | |
} | |
$this->assertEntity($entity); | |
/** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ | |
$order_item = $entity; | |
// The roles this condition allows for | |
$modules = $this->getModules($promotion); | |
// If there are no modules defined, return. | |
// @todo maybe some logging here, if an editor is using this discount they | |
// should have defined modules it is applicable to. | |
if (empty($modules)) { | |
return; | |
} | |
$sub = $this->authmap->getConnectedAccounts($order_item->getOrder() | |
->getCustomer())['generic']; | |
if (!$sub) { | |
return; | |
} | |
$eligible_modules = $this->getEligibleModules($modules, $sub); | |
foreach ($eligible_modules as $module) { | |
$discount_percentage = $this->getSubLeftPercentage($module); | |
$license = $this->getLicenseForModule($module); | |
if ($license) { | |
$prorate_amount = $this->calculateLicenseProratedAmount($license, $order_item, $discount_percentage); | |
} | |
else { | |
$prorate_amount = $this->calculateLegacyProratedAmount($module, $order_item, $discount_percentage); | |
} | |
if (!isset($prorate_amount) || !$prorate_amount) { | |
continue; | |
} | |
$order_item->addAdjustment(new Adjustment([ | |
'type' => 'promotion', | |
// @todo Change to label from UI when added in #2770731. | |
'label' => $promotion->getDisplayName(), | |
'amount' => $prorate_amount->multiply('-1'), | |
'source_id' => $promotion->id(), | |
])); | |
} | |
} | |
protected function assertSub() { | |
} | |
protected function getModules(PromotionInterface $promotion) { | |
$conditions = $promotion->getConditions(); | |
$modules = []; | |
foreach ($conditions as $condition) { | |
if ($condition instanceof ClientProduct) { | |
$modules = $condition->getConfiguration()['modules']; | |
} | |
} | |
return $modules; | |
} | |
protected function getEligibleModules($modules, $sub) { | |
$eligible_modules = []; | |
$subscriptions = $this->connectionService->getAccountRemoteSubscriptionsBySub($sub); | |
foreach ($modules as $role) { | |
if ($active = SubscriptionUtility::userHasActiveSubscriptionForModule($subscriptions, ConnectionService::mapRolesToPlatform($role))) { | |
$eligible_modules[] = $active; | |
} | |
} | |
return $eligible_modules; | |
} | |
protected function getLicenseForModule($module) { | |
$license = $this->licenseStorage->loadByProperties([ | |
'platform_remote_id' => $module['id'], | |
]); | |
// Two licenses can have the same remote ID, we want the most recent | |
if (is_array($license)) { | |
$license = array_pop($license); | |
} | |
return $license ?: FALSE; | |
} | |
protected function getSubLeftPercentage($module) { | |
// How much time was subscription for | |
$sub_length = strtotime($module['endDate'] . " UTC") - strtotime($module['beginDate'] . " UTC"); | |
// How much time is left | |
$sub_left = strtotime($module['endDate']) - $this->time->getRequestTime(); | |
if ($sub_left < 0) { | |
return 0; | |
} | |
return (($sub_left / $sub_length)); | |
} | |
protected function getSubLeftPercentageUsingLegacy($legacyItem) { | |
// How much time was subscription for | |
$sub_length = $legacyItem['endDate'] - $legacyItem['beginDate']; | |
// How much time is left | |
$sub_left = $legacyItem['endDate'] - $this->time->getRequestTime(); | |
if ($sub_left < 0) { | |
return 0; | |
} | |
// What is discount percentage | |
return (($sub_left / $sub_length)); | |
} | |
/** | |
* Calculate prorated amount based on a subscription from legacy. | |
* @param $module | |
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item | |
* @param $discount_percentage | |
* @return Price|bool | |
*/ | |
protected function calculateLegacyProratedAmount($module, OrderItemInterface $order_item, $discount_percentage) { | |
$email = $order_item->getOrder()->getEmail(); | |
$legacyItem = $this->legacyService->getLegacySubscriptionForEmailAndProduct($email, $module['product']); | |
if (!$legacyItem) return FALSE; | |
$legacyItemFubar = strpos($legacyItem, '{'); | |
$legacyItem = substr($legacyItem, $legacyItemFubar); | |
$legacyItem = Json::decode($legacyItem); | |
if (array_key_exists('error', $legacyItem)) { | |
\Drupal::logger('license_prorate') | |
->alert('Error from legacy: ' . $legacyItem['error']); | |
return FALSE; | |
} | |
// Client 2.0 has an issue with start dates being in 1970 so we need to use legacy api to estimate | |
$discount_percentage = $this->getSubLeftPercentageUsingLegacy($legacyItem); | |
/** @var Price $license_to_prorate_cost */ | |
$license_to_prorate_cost = new Price((string) $legacyItem['product_gross'], 'USD'); | |
if ($legacyItem['discount']) { | |
$license_to_prorate_cost->subtract(new Price((string) $legacyItem['discount'], 'USD')); | |
} | |
$prorate_amount = $license_to_prorate_cost->multiply((string) $discount_percentage); | |
$prorate_amount = $this->rounder->round($prorate_amount); | |
// Never discount more than the total price of the order_item. | |
// @todo does this re-calc when coupons are added? | |
$unit_price = $order_item->getAdjustedUnitPrice(); | |
if ($prorate_amount->greaterThan($unit_price)) { | |
$prorate_amount = $unit_price; | |
} | |
return $prorate_amount; | |
} | |
protected function legacyItemIsLongerThanPurchasedItem($legacy_item, OrderItemInterface $orderItem) { | |
// return 3, 6, 18, etc. | |
$newItemLength = $this->getLengthForOrderItem($orderItem); | |
$oldItemLength = $this->legacyService->getLengthForLegacyItem($legacy_item); | |
\Drupal::logger('license_proate') | |
->alert('New Item Length: ' . $newItemLength . '; Old Item Length: ' . $oldItemLength); | |
return $newItemLength >= $oldItemLength; | |
} | |
/** | |
* Calculate prorated amount based on a License that was | |
* purchased in 2.0. | |
* | |
* @param License $license | |
* @param $order_item | |
* @param $discount_percentage | |
* | |
* @return Price|bool | |
*/ | |
protected function calculateLicenseProratedAmount(License $license, OrderItemInterface $order_item, $discount_percentage) { | |
// How much did they pay for this license? | |
$license_order_item = $this->orderItemStorage->loadByProperties([ | |
'license' => $license->id(), | |
]); | |
// If the license wasn't purchased. | |
if (empty($license_order_item)) { | |
return FALSE; | |
} | |
/** @var OrderItemInterface $license_to_prorate_order_item */ | |
$license_to_prorate_order_item = reset($license_order_item); | |
// PL2-1027 | |
if (!$this->newItemIsEqualOrLongerThanOldItem($order_item, $license_to_prorate_order_item)) { | |
return FALSE; | |
} | |
/** @var Price $license_to_prorate_cost */ | |
$license_to_prorate_cost = $license_to_prorate_order_item->getAdjustedUnitPrice(); | |
$prorate_amount = $license_to_prorate_cost->multiply((string) $discount_percentage); | |
$prorate_amount = $this->rounder->round($prorate_amount); | |
// Never discount more than the total price of the order_item. | |
// @todo does this re-calc when coupons are added? | |
$unit_price = $order_item->getAdjustedUnitPrice(); | |
if ($prorate_amount->greaterThan($unit_price)) { | |
$prorate_amount = $unit_price; | |
} | |
return $prorate_amount; | |
} | |
/** | |
* PL2-1027 Hat tip @EmilyByrnes for figuring this out. | |
* | |
* We do not allow a user to receive a pro-rated discount if purchasing | |
* an item that is of lesser length. The reason for this is that eComm | |
* currently does not have a method of altering subscriptions *except* for | |
* addTime. | |
* | |
* As a result a user could purchase a 24 month subscription, upgrade to a 12 | |
* month 360, get their pro-rated discount and end up with 12 months FF and | |
* EXP but still have 24 months of Qmax. | |
* | |
* Therefore, don't apply the pro-rated discount if the length is less :) | |
* | |
* @param \Drupal\commerce_order\Entity\OrderItem $newItem | |
* @param \Drupal\commerce_order\Entity\OrderItem $oldItem | |
* @return bool | |
*/ | |
protected function newItemIsEqualOrLongerThanOldItem(OrderItem $newItem, OrderItem $oldItem) { | |
$newItemLength = $this->getLengthForOrderItem($newItem); | |
$oldItemLength = $this->getLengthForOrderItem($oldItem); | |
return $newItemLength >= $oldItemLength; | |
} | |
protected function getLengthForOrderItem(OrderItem $orderItem) { | |
/** @var ProductVariation $variation */ | |
$variation = $orderItem->getPurchasedEntity(); | |
/** @var ProductAttributeValue $length_attribute */ | |
$length_attribute = $variation->getAttributeValue('attribute_length'); | |
return $length_attribute->label(); | |
} | |
protected function hasEnoughTimeLeft($end_date_string) { | |
// PL2-1017 - remove the restriction on 90 days | |
return TRUE; | |
// Woot | |
$now = new \DateTime(); | |
$now->setTimezone(new \DateTimeZone('UTC')); | |
$end = new \DateTime($end_date_string); | |
$interval = $end->diff($now); | |
$days_diff = $interval->format('%a'); | |
return (int) $days_diff > 90; | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment