View source
<?php
namespace Drupal\commerce_recurring;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_payment\Exception\HardDeclineException;
use Drupal\commerce_recurring\Entity\SubscriptionInterface;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
class RecurringOrderManager implements RecurringOrderManagerInterface {
protected $entityTypeManager;
protected $time;
public function __construct(EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
$this->entityTypeManager = $entity_type_manager;
$this->time = $time;
}
public function startTrial(SubscriptionInterface $subscription) {
$state = $subscription
->getState()
->getId();
if ($state != 'trial') {
throw new \InvalidArgumentException(sprintf('Unexpected subscription state "%s".', $state));
}
$billing_schedule = $subscription
->getBillingSchedule();
if (!$billing_schedule
->getPlugin()
->allowTrials()) {
throw new \InvalidArgumentException(sprintf('The billing schedule "%s" does not allow trials.', $billing_schedule
->id()));
}
$start_date = $subscription
->getTrialStartDate();
$end_date = $subscription
->getTrialEndDate();
$trial_period = new BillingPeriod($start_date, $end_date);
$order = $this
->createOrder($subscription, $trial_period);
$this
->applyCharges($order, $subscription, $trial_period);
$subscription
->getType()
->onSubscriptionTrialStart($subscription, $order);
$order
->save();
$subscription
->addOrder($order);
$subscription
->save();
return $order;
}
public function startRecurring(SubscriptionInterface $subscription) {
$state = $subscription
->getState()
->getId();
if ($state != 'active') {
throw new \InvalidArgumentException(sprintf('Unexpected subscription state "%s".', $state));
}
$start_date = $subscription
->getStartDate();
$billing_schedule = $subscription
->getBillingSchedule();
$billing_period = $billing_schedule
->getPlugin()
->generateFirstBillingPeriod($start_date);
$subscription
->setNextRenewalTime($billing_period
->getEndDate()
->getTimestamp());
$order = $this
->createOrder($subscription, $billing_period);
$this
->applyCharges($order, $subscription, $billing_period);
$subscription
->getType()
->onSubscriptionActivate($subscription, $order);
$order
->save();
$subscription
->addOrder($order);
$subscription
->save();
return $order;
}
public function refreshOrder(OrderInterface $order) {
$billing_period_item = $order
->get('billing_period')
->first();
$billing_period = $billing_period_item
->toBillingPeriod();
$subscriptions = $this
->collectSubscriptions($order);
$payment_method = $this
->selectPaymentMethod($subscriptions);
$billing_profile = $payment_method ? $payment_method
->getBillingProfile() : NULL;
$payment_gateway_id = $payment_method ? $payment_method
->getPaymentGatewayId() : NULL;
$order
->set('billing_profile', $billing_profile);
$order
->set('payment_method', $payment_method);
$order
->set('payment_gateway', $payment_gateway_id);
foreach ($subscriptions as $subscription) {
$this
->applyCharges($order, $subscription, $billing_period);
}
$order_items = $order
->getItems();
if (!$order_items) {
$order
->set('state', 'canceled');
}
foreach ($order_items as $order_item) {
if ($order_item
->isNew()) {
$order_item->order_id->entity = $order;
}
}
}
public function closeOrder(OrderInterface $order) {
$order_state = $order
->getState()
->getId();
if ($order
->isPaid()) {
if (in_array('mark_paid', array_keys($order
->getState()
->getTransitions()))) {
$order
->getState()
->applyTransitionById('mark_paid');
$order
->save();
}
}
if (in_array($order_state, [
'canceled',
'completed',
]) || $order
->isPaid()) {
return;
}
if ($order_state == 'draft') {
$order
->getState()
->applyTransitionById('place');
$order
->save();
}
$subscriptions = $this
->collectSubscriptions($order);
$payment_method = $this
->selectPaymentMethod($subscriptions);
if (!$payment_method) {
throw new HardDeclineException('Payment method not found.');
}
$payment_gateway = $payment_method
->getPaymentGateway();
if (!$payment_gateway) {
throw new HardDeclineException('Payment gateway not found');
}
$payment_gateway_plugin = $payment_gateway
->getPlugin();
if (!$order
->isPaid()) {
$payment_storage = $this->entityTypeManager
->getStorage('commerce_payment');
$payment = $payment_storage
->create([
'payment_gateway' => $payment_gateway
->id(),
'payment_method' => $payment_method
->id(),
'order_id' => $order
->id(),
'amount' => $order
->getTotalPrice(),
'state' => 'new',
]);
$payment_gateway_plugin
->createPayment($payment);
$order
->getState()
->applyTransitionById('mark_paid');
$order
->save();
}
}
public function renewOrder(OrderInterface $order) {
$subscriptions = $this
->collectSubscriptions($order);
$subscription = reset($subscriptions);
if (!$subscription || $subscription
->getState()
->getId() != 'active') {
return NULL;
}
$billing_schedule = $subscription
->getBillingSchedule();
$start_date = $subscription
->getStartDate();
$billing_period_item = $order
->get('billing_period')
->first();
$current_billing_period = $billing_period_item
->toBillingPeriod();
$next_billing_period = $billing_schedule
->getPlugin()
->generateNextBillingPeriod($start_date, $current_billing_period);
$next_order = $this
->createOrder($subscription, $next_billing_period);
$this
->applyCharges($next_order, $subscription, $next_billing_period);
$subscription
->getType()
->onSubscriptionRenew($subscription, $order, $next_order);
$next_order
->save();
$subscription
->addOrder($next_order);
$subscription
->setRenewedTime($this->time
->getCurrentTime());
$subscription
->setNextRenewalTime($next_billing_period
->getEndDate()
->getTimestamp());
$subscription
->save();
return $next_order;
}
public function collectSubscriptions(OrderInterface $order) {
$subscriptions = [];
foreach ($order
->getItems() as $order_item) {
if ($order_item
->get('subscription')
->isEmpty()) {
continue;
}
$subscription = $order_item
->get('subscription')->entity;
if ($subscription) {
$subscriptions[$subscription
->id()] = $subscription;
}
}
return $subscriptions;
}
protected function createOrder(SubscriptionInterface $subscription, BillingPeriod $billing_period) {
$payment_method = $subscription
->getPaymentMethod();
$order = $this->entityTypeManager
->getStorage('commerce_order')
->create([
'type' => 'recurring',
'store_id' => $subscription
->getStoreId(),
'uid' => $subscription
->getCustomerId(),
'billing_profile' => $payment_method ? $payment_method
->getBillingProfile() : NULL,
'payment_method' => $payment_method,
'payment_gateway' => $payment_method ? $payment_method
->getPaymentGatewayId() : NULL,
'billing_period' => $billing_period,
'billing_schedule' => $subscription
->getBillingSchedule(),
]);
return $order;
}
protected function applyCharges(OrderInterface $order, SubscriptionInterface $subscription, BillingPeriod $billing_period) {
$order_item_storage = $this->entityTypeManager
->getStorage('commerce_order_item');
$existing_order_items = [];
foreach ($order
->getItems() as $order_item) {
if ($order_item
->get('subscription')->target_id == $subscription
->id()) {
$existing_order_items[] = $order_item;
}
}
if ($subscription
->getState()
->getId() == 'trial') {
$charges = $subscription
->getType()
->collectTrialCharges($subscription, $billing_period);
}
else {
$charges = $subscription
->getType()
->collectCharges($subscription, $billing_period);
}
$order_items = [];
foreach ($charges as $charge) {
$order_item = array_shift($existing_order_items);
if (!$order_item) {
$order_item = $order_item_storage
->create([
'type' => $this
->getOrderItemTypeId($subscription),
'order_id' => $order
->id(),
'subscription' => $subscription
->id(),
]);
}
$order_item
->set('purchased_entity', $charge
->getPurchasedEntity());
$order_item
->setTitle($charge
->getTitle());
$order_item
->setQuantity($charge
->getQuantity());
$order_item
->set('billing_period', $charge
->getBillingPeriod());
$order_item
->setUnitPrice($charge
->getUnitPrice());
if ($charge
->needsProration()) {
$prorater = $subscription
->getBillingSchedule()
->getProrater();
$prorated_unit_price = $prorater
->prorateOrderItem($order_item, $charge
->getBillingPeriod(), $charge
->getFullBillingPeriod());
$order_item
->setUnitPrice($prorated_unit_price, TRUE);
}
if ($order_item
->isNew()) {
$order_item
->save();
}
$order_items[] = $order_item;
}
$order
->setItems($order_items);
if ($existing_order_items) {
$order_item_storage
->delete($existing_order_items);
}
}
protected function selectPaymentMethod(array $subscriptions) {
$payment_methods = [];
foreach ($subscriptions as $subscription) {
if ($payment_method = $subscription
->getPaymentMethod()) {
$payment_methods[$payment_method
->id()] = $payment_method;
}
}
krsort($payment_methods, SORT_NUMERIC);
$payment_method = reset($payment_methods);
return $payment_method ?: NULL;
}
protected function getOrderItemTypeId(SubscriptionInterface $subscription) {
if ($purchasable_entity_type_id = $subscription
->getType()
->getPurchasableEntityTypeId()) {
return 'recurring_' . str_replace('commerce_', '', $purchasable_entity_type_id);
}
else {
return 'recurring_standalone';
}
}
}