Your IP : 216.73.216.43


Current Path : /home/rtorresani/www/vendor/stripe/module-payments/Helper/
Upload File :
Current File : //home/rtorresani/www/vendor/stripe/module-payments/Helper/SubscriptionUpdates.php

<?php

namespace StripeIntegration\Payments\Helper;

use StripeIntegration\Payments\Helper\Logger;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\LocalizedException;

class SubscriptionUpdates
{
    protected $_refundCreditBalance = false;

    private $addressHelper;
    private $creditmemoHelper;
    private $stripeSubscriptionFactory;
    private $subscriptionsHelper;
    private $customerSession;
    private $shippingInformationFactory;
    private $shippingInformationManagement;
    private $storeManager;
    private $cartManagement;
    private $checkoutSession;
    private $eventManager;
    private $config;
    private $helper;

    public function __construct(
        \Magento\Customer\Model\Session $customerSession,
        \Magento\Checkout\Api\Data\ShippingInformationInterfaceFactory $shippingInformationFactory,
        \Magento\Checkout\Api\ShippingInformationManagementInterface $shippingInformationManagement,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Quote\Api\CartManagementInterface $cartManagement,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Checkout\Model\Session $checkoutSession,
        \StripeIntegration\Payments\Helper\Generic $helper,
        \StripeIntegration\Payments\Helper\Address $addressHelper,
        \StripeIntegration\Payments\Helper\Subscriptions $subscriptionsHelper,
        \StripeIntegration\Payments\Helper\Creditmemo $creditmemoHelper,
        \StripeIntegration\Payments\Model\Stripe\SubscriptionFactory $stripeSubscriptionFactory,
        \StripeIntegration\Payments\Model\Config $config
    )
    {
        $this->customerSession = $customerSession;
        $this->shippingInformationFactory = $shippingInformationFactory;
        $this->shippingInformationManagement = $shippingInformationManagement;
        $this->storeManager = $storeManager;
        $this->cartManagement = $cartManagement;
        $this->eventManager = $eventManager;
        $this->checkoutSession = $checkoutSession;
        $this->helper = $helper;
        $this->addressHelper = $addressHelper;
        $this->subscriptionsHelper = $subscriptionsHelper;
        $this->creditmemoHelper = $creditmemoHelper;
        $this->stripeSubscriptionFactory = $stripeSubscriptionFactory;
        $this->config = $config;
    }

    public function getCurrentStripeSubscriptionModel()
    {
        $updateDetails = $this->getSubscriptionUpdateDetails();

        if (!$updateDetails)
        {
            return null;
        }

        $model = $this->stripeSubscriptionFactory->create()->fromSubscriptionId($updateDetails['_data']['subscription_id']);

        return $model;
    }

    public function getSubscriptionUpdateDetails()
    {
        $updateDetails = $this->checkoutSession->getSubscriptionUpdateDetails();

        if (isset($updateDetails['_data']['subscription_id']))
        {
            return $updateDetails;
        }

        return null;
    }

    // The logic generally follows the premise that if a new payment is collected, a new order must be created to provide
    // an invoice to the customer and record the transaction internally. In all other cases (including proration refunds)
    // there is no need to create a new order because a) no transaction took place, and b) the merchant should not fulfil
    // an order just yet, they should wait until a payment for the new goods is received.
    public function shouldPlaceNewOrder(\StripeIntegration\Payments\Model\Stripe\Subscription $currentStripeSubscription)
    {
        $quote = $this->helper->getQuote();

        $subscription = $this->subscriptionsHelper->getSubscriptionFromQuote($quote);
        $profile = $subscription['profile'];
        $magentoAmount = $this->subscriptionsHelper->getSubscriptionTotalWithDiscountAdjustmentFromProfile($profile);
        $stripeAmount = $this->helper->convertMagentoAmountToStripeAmount($magentoAmount, $profile["currency"]);

        $newProductIds = [ $profile["product_id"] ];

        $hasProrations = $currentStripeSubscription->useProrations($stripeAmount, $newProductIds);

        $this->_refundCreditBalance = false;

        if (!$hasProrations)
        {
            // There is no scenario where we collect a payment if there are no prorations
            return false;
        }
        else
        {
            $priceChange = $currentStripeSubscription->getPriceChange($stripeAmount);

            if ($priceChange > 0)
            {
                // A new payment will be collected, so we want to create a new order
                return true;
            }
            else
            {
                // An online credit memo will be created against the original order.
                $this->validateCreditBalance($currentStripeSubscription->getStripeObject());
                $this->_refundCreditBalance = true;
                return false;
            }
        }
    }

    // Make sure that the customer does not already have a credit balance from an external integration.
    // The prorated transaction amount should be deterministic so that we record the transaction in Magento.
    protected function validateCreditBalance(\Stripe\Subscription $subscription)
    {
        $customer = $this->config->getStripeClient()->customers->retrieve($subscription->customer);

        if ($customer->balance != 0)
            throw new LocalizedException(__("A prorated subscription update is not possible because your customer account already has a credit balance. Please contact us for assistance."));
    }

    public function performUpdate($billingAddress, $shippingAddress = null, $shippingMethod = null, $couponCode = null)
    {
        if (!$this->customerSession->isLoggedIn())
        {
            throw new \Exception("Please log in and try again.");
        }

        $quote = $this->helper->getQuote();
        if (!$quote || !$quote->getId())
            throw new \Exception("The customer session does not have a quote.");

        $currentStripeSubscriptionModel = $this->getCurrentStripeSubscriptionModel();

        // Set the billing address
        $data = $this->addressHelper->filterAddressData($billingAddress);
        $quote->getBillingAddress()->addData($data);

        // Set the shipping address
        if (!$quote->getIsVirtual())
        {
            $data = $this->addressHelper->filterAddressData($shippingAddress);
            $quote->getShippingAddress()->addData($data);

            // Set the shipping method
            if (!empty($shippingMethod['carrier_code']) && !empty($shippingMethod['method_code']))
            {
                /** @var \Magento\Checkout\Api\Data\ShippingInformationInterface $shippingInformation */
                $shippingInformation = $this->shippingInformationFactory->create();
                $shippingInformation
                    ->setShippingAddress($quote->getShippingAddress())
                    ->setShippingCarrierCode($shippingMethod['carrier_code'])
                    ->setShippingMethodCode($shippingMethod['method_code']);

                $this->shippingInformationManagement->saveAddressInformation($quote->getId(), $shippingInformation);
            }
        }

        // Set the coupon code
        if ($couponCode)
        {
            $quote->setCouponCode($couponCode);
        }
        else
        {
            $quote->setCouponCode(null);
        }

        // Update totals
        $quote->setTotalsCollectedFlag(false);
        $quote->collectTotals();

        // For multi-stripe account configurations, load the correct Stripe API key from the correct store view
        $this->storeManager->setCurrentStore($quote->getStoreId());
        $this->config->initStripe();

        $quote->setCheckoutMethod(\Magento\Checkout\Model\Type\Onepage::METHOD_CUSTOMER);

        $quote->getPayment()->importData(['method' => 'stripe_payments', 'additional_data' => [
            'subscription_update' => true,
        ]]);

        if ($this->shouldPlaceNewOrder($currentStripeSubscriptionModel))
        {
            // Place Order
            /** @var \Magento\Sales\Model\Order $order */
            $order = $this->cartManagement->submit($quote);
            if ($order)
            {
                $this->helper->setProcessingState($order, __("The subscription has been updated successfully."));
                $this->helper->saveOrder($order);

                $this->eventManager->dispatch(
                    'checkout_type_onepage_save_order_after',
                    ['order' => $order, 'quote' => $quote]
                );

                $this->checkoutSession
                    ->setLastQuoteId($quote->getId())
                    ->setLastSuccessQuoteId($quote->getId())
                    ->setLastOrderId($order->getId())
                    ->setLastRealOrderId($order->getIncrementId())
                    ->setLastOrderStatus($order->getStatus());

                /** @var \Magento\Framework\DataObject $payment */
                $payment = $order->getPayment();
                $invoiceAmountPaid = $payment->getAdditionalInformation("stripe_invoice_amount_paid");
                $invoiceCurrency = $payment->getAdditionalInformation("stripe_invoice_currency");
                $this->creditmemoHelper->refundUnderchargedOrder($order, $invoiceAmountPaid, $invoiceCurrency);
            }
            else
            {
                throw new \Exception("The order could not be placed.");
            }

            $this->eventManager->dispatch(
                'checkout_submit_all_after',
                [
                    'order' => $order,
                    'quote' => $quote
                ]
            );
        }
        else
        {
            $subscriptionUpdateDetails = $this->getSubscriptionUpdateDetails();
            if (!$subscriptionUpdateDetails)
                throw new \Exception("The subscription update details could not be read from the checkout session.");

            $latestInvoiceId = $currentStripeSubscriptionModel->getStripeObject()->latest_invoice;

            $currentStripeSubscriptionModel->performUpdate();

            $quote->setIsUsedForRecurringOrders(true);
            $quote->setIsActive(false);
            $this->helper->saveQuote($quote);

            $originalOrder = null;
            try
            {
                if (empty($subscriptionUpdateDetails['_data']['original_order_increment_id']))
                {
                    throw new \Exception("The subscription update data did not reference an original order increment ID.");
                }

                $originalOrder = $this->helper->loadOrderByIncrementId($subscriptionUpdateDetails['_data']['original_order_increment_id']);
                $paymentAmount = $currentStripeSubscriptionModel->getFormattedAmount();
                $billingDate = date("jS M Y", $subscriptionUpdateDetails['_data']['current_period_end']);
                $comment = __("Successfully updated subscription upon customer request. A payment of %1 will be collected on %2. A new order will be created from cart ID %3 upon payment collection.", $paymentAmount, $billingDate, $quote->getId());
                $this->helper->addOrderComment($comment, $originalOrder);
                $this->helper->saveOrder($originalOrder);
            }
            catch (\Exception $e)
            {
                $this->helper->logError("Could not add subscription update order comments: " . $e->getMessage());
            }

            $this->refundCreditBalance($currentStripeSubscriptionModel->getStripeObject(), $latestInvoiceId, $originalOrder);
        }
    }

    public function refundCreditBalance(\Stripe\Subscription $subscription, $refundFromInvoiceId, $order = null)
    {
        if (!$this->_refundCreditBalance)
            return;

        // Once we have refunded the balance, a charge.refunded event will arrive and will create a credit memo against the original order
        try
        {
            /** @var \Stripe\Invoice $latestInvoice */
            $latestInvoice = $this->config->getStripeClient()->invoices->retrieve($refundFromInvoiceId, ['expand' => ['payment_intent', 'customer']]);

            if (empty($latestInvoice->customer->id))
                throw new \Exception("The last invoice for this subscription is not associated with a customer.");

            if ($latestInvoice->customer->balance >= 0)
                return;

            if (empty($latestInvoice->payment_intent->id))
                throw new \Exception("The last invoice for this subscription does not have a payment intent.");

            if (-$latestInvoice->customer->balance > $latestInvoice->payment_intent->amount)
                throw new \Exception("The customer credit balance is larger than the available amount to refund.");

            if ($order)
            {
                $refundAmount = $this->helper->formatStripePrice(-$latestInvoice->customer->balance, $latestInvoice->currency);
                $comment = __("The customer has an unused credit balance of %1 from their previous subscription. We will refund the amount to the customer.", $refundAmount);
                $this->helper->addOrderComment($comment, $order);
                $this->helper->saveOrder($order);
            }

            $this->config->getStripeClient()->refunds->create([
              'payment_intent' => $latestInvoice->payment_intent->id,
              'amount' => -$latestInvoice->customer->balance
            ]);

            $this->config->getStripeClient()->customers->update($latestInvoice->customer->id, [
              'balance' => 0
            ]);
        }
        catch (\Exception $e)
        {
            $this->helper->logError("Unable to refund customer credit balance: " . $e->getMessage(), $e->getTraceAsString());
            return;
        }
    }
}