Your IP : 216.73.217.95


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

<?php

namespace StripeIntegration\Payments\Observer;

use Magento\Framework\Event\ObserverInterface;
use StripeIntegration\Payments\Helper\Logger;
use StripeIntegration\Payments\Exception\WebhookException;
use StripeIntegration\Payments\Exception\MissingOrderException;

class WebhooksObserver implements ObserverInterface
{
    private $addressHelper;
    private $cache;
    private $checkoutSessionHelper;
    private $config;
    private $creditmemoHelper;
    private $eventManager;
    private $invoiceFactory;
    private $invoiceRepository;
    private $invoiceService;
    private $orderCommentSender;
    private $orderHelper;
    private $orderRepository;
    private $paymentElementFactory;
    private $paymentIntentFactory;
    private $paymentsHelper;
    private $recurringOrderHelper;
    private $subscriptionFactory;
    private $subscriptionsHelper;
    private $transactionBuilder;
    private $webhooksHelper;

    public function __construct(
        \StripeIntegration\Payments\Helper\Webhooks $webhooksHelper,
        \StripeIntegration\Payments\Helper\Generic $paymentsHelper,
        \StripeIntegration\Payments\Helper\Subscriptions $subscriptionsHelper,
        \StripeIntegration\Payments\Helper\Address $addressHelper,
        \StripeIntegration\Payments\Helper\Order $orderHelper,
        \StripeIntegration\Payments\Helper\Creditmemo $creditmemoHelper,
        \StripeIntegration\Payments\Model\InvoiceFactory $invoiceFactory,
        \StripeIntegration\Payments\Model\PaymentIntentFactory $paymentIntentFactory,
        \StripeIntegration\Payments\Model\Config $config,
        \StripeIntegration\Payments\Model\SubscriptionFactory $subscriptionFactory,
        \StripeIntegration\Payments\Model\PaymentElementFactory $paymentElementFactory,
        \StripeIntegration\Payments\Helper\RecurringOrder $recurringOrderHelper,
        \StripeIntegration\Payments\Helper\CheckoutSession $checkoutSessionHelper,
        \Magento\Sales\Model\Order\Email\Sender\OrderCommentSender $orderCommentSender,
        \Magento\Sales\Model\Service\InvoiceService $invoiceService,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Framework\App\CacheInterface $cache,
        \Magento\Sales\Model\Order\Payment\Transaction\Builder $transactionBuilder,
        \Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
        \Magento\Sales\Api\InvoiceRepositoryInterface $invoiceRepository
    )
    {
        $this->webhooksHelper = $webhooksHelper;
        $this->paymentsHelper = $paymentsHelper;
        $this->subscriptionsHelper = $subscriptionsHelper;
        $this->addressHelper = $addressHelper;
        $this->orderHelper = $orderHelper;
        $this->creditmemoHelper = $creditmemoHelper;
        $this->invoiceFactory = $invoiceFactory;
        $this->paymentIntentFactory = $paymentIntentFactory;
        $this->config = $config;
        $this->subscriptionFactory = $subscriptionFactory;
        $this->paymentElementFactory = $paymentElementFactory;
        $this->recurringOrderHelper = $recurringOrderHelper;
        $this->checkoutSessionHelper = $checkoutSessionHelper;
        $this->orderCommentSender = $orderCommentSender;
        $this->eventManager = $eventManager;
        $this->invoiceService = $invoiceService;
        $this->cache = $cache;
        $this->transactionBuilder = $transactionBuilder;
        $this->orderRepository = $orderRepository;
        $this->invoiceRepository = $invoiceRepository;
    }

    protected function orderAgeLessThan($minutes, $order)
    {
        $created = strtotime($order->getCreatedAt());
        $now = time();
        return (($now - $created) < ($minutes * 60));
    }

    public function wasCapturedFromAdmin($object)
    {
        if (!empty($object['id']) && $this->cache->load("admin_captured_" . $object['id']))
        {
            return true;
        }

        if (!empty($object['payment_intent']) && is_string($object['payment_intent']) && $this->cache->load("admin_captured_" . $object['payment_intent']))
        {
            return true;
        }

        return false;
    }

    public function wasRefundedFromAdmin($object)
    {
        if (!empty($object['id']) && $this->cache->load("admin_refunded_" . $object['id']))
            return true;

        return false;
    }

    /**
     * @return void
     */
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $eventName = $observer->getEvent()->getName();
        $arrEvent = $observer->getData('arrEvent');
        $stdEvent = $observer->getData('stdEvent');
        $object = $observer->getData('object');
        $paymentMethod = $observer->getData('paymentMethod');
        $isAsynchronousPaymentMethod = false;

        switch ($eventName)
        {
            case 'stripe_payments_webhook_checkout_session_expired':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                $this->addOrderComment($order, __("Stripe Checkout session has expired without a payment."));

                if ($this->paymentsHelper->isPendingCheckoutOrder($order))
                    $this->paymentsHelper->cancelOrCloseOrder($order);

                break;

            case 'stripe_payments_webhook_payment_intent_processing':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                if (!$order->getEmailSent())
                {
                    $this->paymentsHelper->sendNewOrderEmailFor($order);
                }

                break;

            // Called when placing a trial subscription order with Stripe Checkout
            // Performs order post processing after a successful setup intent
            case 'stripe_payments_webhook_checkout_session_completed':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                $quote = $this->paymentsHelper->loadQuoteById($order->getQuoteId());
                if ($quote && $quote->getIsActive())
                {
                    $quote->setIsActive(false);
                    $this->paymentsHelper->saveQuote($quote);
                }

                if (empty($object['subscription']) || empty($object['setup_intent']))
                {
                    // We are not interested in processing any other cases here.
                    return;
                }

                $subscription = $this->config->getStripeClient()->subscriptions->retrieve($object['subscription']);

                $this->processTrialingSubscriptionOrder($order, $subscription);

                break;

            // Creates an invoice for an order when the payment is captured from the Stripe dashboard
            case 'stripe_payments_webhook_charge_captured':

                if ($this->wasCapturedFromAdmin($object))
                    return;

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                $payment = $order->getPayment();

                if (empty($object['payment_intent']))
                    return;

                $paymentIntentId = $object['payment_intent'];

                $chargeAmount = $this->paymentsHelper->convertStripeAmountToOrderAmount($object['amount_captured'], $object['currency'], $order);
                $transactionType = \Magento\Sales\Model\Order\Payment\Transaction::TYPE_CAPTURE;
                $transaction = $this->paymentsHelper->addTransaction($order, $paymentIntentId, $transactionType, $paymentIntentId);
                $transaction->setAdditionalInformation("amount", $chargeAmount);
                $transaction->setAdditionalInformation("currency", $object['currency']);
                $transaction->save();

                $humanReadableAmount = $this->paymentsHelper->addCurrencySymbol($chargeAmount, $object['currency']);
                $comment = __("%1 amount of %2 via Stripe. Transaction ID: %3", __("Captured"), $humanReadableAmount, $paymentIntentId);
                $order->addStatusToHistory(false, $comment, $isCustomerNotified = false);
                $this->orderRepository->save($order);

                $params = [
                    "amount" => $object['amount_captured'],
                    "currency" => $object['currency']
                ];

                $captureCase = \Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE;

                $this->paymentsHelper->invoiceOrder($order, $paymentIntentId, $captureCase, $params);

                break;

            case 'stripe_payments_webhook_review_closed':

                if (empty($object['payment_intent']))
                    return;

                $orders = $this->webhooksHelper->loadOrderFromEvent($arrEvent, true);

                foreach ($orders as $order)
                {
                    $this->webhooksHelper->detectRaceCondition($order->getIncrementId(), ['charge.refunded']);
                }

                foreach ($orders as $order)
                {
                    $this->eventManager->dispatch(
                        'stripe_payments_review_closed_before',
                        ['order' => $order, 'object' => $object]
                    );

                    if ($object['reason'] == "approved")
                    {
                        if ($order->canUnhold())
                            $order->unhold();

                        $comment = __("The payment has been approved via Stripe.");
                        $order->addStatusToHistory(false, $comment, $isCustomerNotified = false);
                        $this->paymentsHelper->saveOrder($order);
                    }
                    else if ($object['reason'] == "refunded_as_fraud")
                    {
                        if ($order->canUnhold())
                            $order->unhold();

                        $comment = __("The payment has been rejected as fraudulent via Stripe.");
                        $order->setState($order::STATE_PAYMENT_REVIEW);
                        $order->addStatusToHistory($order::STATUS_FRAUD, $comment, $isCustomerNotified = false);
                        $this->paymentsHelper->saveOrder($order);
                    }
                    else
                    {
                        $comment = __("The payment was canceled through Stripe with reason: %1.", ucfirst(str_replace("_", " ", $object['reason'])));
                        $order->addStatusToHistory(false, $comment, $isCustomerNotified = false);
                        $this->paymentsHelper->saveOrder($order);
                    }

                    $this->eventManager->dispatch(
                        'stripe_payments_review_closed_after',
                        ['order' => $order, 'object' => $object]
                    );
                }

                break;

            case 'stripe_payments_webhook_customer_subscription_updated':

                try
                {
                    $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                }
                catch (WebhookException $e)
                {
                    if ($e->statusCode == 202 && isset($object['metadata']['Original Order #']))
                    {
                        // This is a subscription update which did not generate a new order.
                        // Orders are not generated when there is no payment collected
                        // So there is nothing to do here, skip the case
                        break;
                    }
                    else
                    {
                        throw $e;
                    }
                }

                if (empty($order->getPayment()))
                    throw new WebhookException("Order #%1 does not have any associated payment details.", $order->getIncrementId());

                $paymentMethod = $order->getPayment()->getMethod();
                $invoiceId = $stdEvent->data->object->latest_invoice;
                if (empty($invoiceId))
                {
                    // This is a new subscription purchase, not an update
                    return;
                }
                $invoiceParams = [
                    'expand' => [
                        'subscription',
                        'payment_intent'
                    ]
                ];

                $invoice = $this->config->getStripeClient()->invoices->retrieve($invoiceId, $invoiceParams);

                $this->webhooksHelper->setPaymentDescriptionAfterSubscriptionUpdate($order, $invoice);

                break;

            case 'stripe_payments_webhook_customer_subscription_created':

                $subscription = $stdEvent->data->object;

                try
                {
                    $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                    $this->subscriptionsHelper->updateSubscriptionEntry($subscription, $order);
                }
                catch (\Exception $e)
                {
                    if ($object['status'] == "incomplete" || $object['status'] == "trialing")
                    {
                        // A PaymentElement has created an incomplete subscription which has no order yet
                        $this->subscriptionsHelper->updateSubscriptionEntry($subscription, null);
                    }
                    else
                    {
                        throw $e;
                    }
                }

                break;

            case 'stripe_payments_webhook_invoice_voided':
            case 'stripe_payments_webhook_invoice_marked_uncollectible':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                switch ($order->getPayment()->getMethod())
                {
                    case "stripe_payments_invoice":
                        $this->webhooksHelper->refundOfflineOrCancel($order);
                        $comment = __("The invoice was voided from the Stripe Dashboard.");
                        $order->addStatusToHistory(false, $comment, $isCustomerNotified = false);
                        $this->paymentsHelper->saveOrder($order);
                        break;
                }

                break;

            case 'stripe_payments_webhook_charge_refunded':

                if ($this->wasRefundedFromAdmin($object))
                    return;

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                $result = $this->creditmemoHelper->refundFromStripeDashboard($order, $object);
                break;

            case 'stripe_payments_webhook_setup_intent_canceled':
            case 'stripe_payments_webhook_payment_intent_canceled':

                if ($object["status"] != "canceled")
                    break;

                $orders = $this->webhooksHelper->loadOrderFromEvent($arrEvent, true);

                foreach ($orders as $order)
                {
                    if ($object["cancellation_reason"] == "abandoned")
                    {
                        $msg = __("Customer abandoned the cart. The payment session has expired.");
                        $this->addOrderComment($order, $msg);
                        $this->paymentsHelper->cancelOrCloseOrder($order);
                    }
                }
                break;

            case 'stripe_payments_webhook_payment_intent_succeeded':

                break;

            case 'stripe_payments_webhook_setup_intent_setup_failed':
            case 'stripe_payments_webhook_payment_intent_payment_failed':

                $orders = $this->webhooksHelper->loadOrderFromEvent($arrEvent, true);

                foreach ($orders as $order)
                {
                    if (!empty($object['last_payment_error']['message']))
                        $lastError = $object['last_payment_error'];
                    elseif (!empty($object['last_setup_error']['message']))
                        $lastError = $object['last_setup_error'];
                    else
                        $lastError = null;

                    if (!empty($lastError['message'])) // This is set with Stripe Checkout / redirect flow
                    {
                        switch ($lastError['code'])
                        {
                            case 'payment_intent_authentication_failure':
                                $msg = __("Payment authentication failed.");
                                break;
                            case 'payment_intent_payment_attempt_failed':
                                if (strpos($lastError['message'], "expired") !== false)
                                {
                                    $msg = __("Customer abandoned the cart. The payment session has expired.");
                                    $this->paymentsHelper->cancelOrCloseOrder($order);
                                }
                                else
                                    $msg = __("Payment failed: %1", $lastError['message']);
                                break;
                            default:
                                $msg = __("Payment failed: %1", $lastError['message']);
                                break;
                        }
                    }
                    else if (!empty($object['failure_message']))
                        $msg = __("Payment failed: %1", $object['failure_message']);
                    else if (!empty($object["outcome"]["seller_message"]))
                        $msg = __("Payment failed: %1", $object["outcome"]["seller_message"]);
                    else
                        $msg = __("Payment failed.");

                    $this->addOrderComment($order, $msg);
                }

                break;

            case 'stripe_payments_webhook_payment_method_attached':

                $this->deduplicatePaymentMethod($object);

                break;

            case 'stripe_payments_webhook_setup_intent_succeeded':

                try
                {
                    $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                }
                catch (MissingOrderException $e)
                {
                    // We get here when the customer adds a new payment method from the customer account section.
                    break;
                }

                // In all other cases, process trial subscription orders for which no charge.succeeded event will be received
                $paymentElement = $this->paymentElementFactory->create()->load($object['id'], 'setup_intent_id');
                if (!$paymentElement->getId())
                    break;

                if (!$paymentElement->getSubscriptionId())
                    break;

                $subscription = $this->config->getStripeClient()->subscriptions->retrieve($paymentElement->getSubscriptionId());

                $updateData = [];

                if (empty($subscription->metadata->{"Order #"}))
                {
                    // With PaymentElement subscriptions, the subscription object is created before the order is placed,
                    // and thus it does not have the order number at creation time.
                    $updateData["metadata"] = ["Order #" => $order->getIncrementId()];
                }

                if (!empty($object['payment_method']))
                {
                    $updateData['default_payment_method'] = $object['payment_method'];
                }

                if (!empty($updateData))
                {
                    $subscription = $this->config->getStripeClient()->subscriptions->update($subscription->id, $updateData);
                }

                $this->processTrialingSubscriptionOrder($order, $subscription);

                break;

            case 'stripe_payments_webhook_source_chargeable':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                $this->webhooksHelper->charge($order, $object);
                break;

            case 'stripe_payments_webhook_source_canceled':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                $canceled = $this->paymentsHelper->cancelOrCloseOrder($order);
                if ($canceled)
                    $this->addOrderCommentWithEmail($order, "Sorry, your order has been canceled because a payment request was sent to your bank, but we did not receive a response back. Please contact us or place your order again.");
                break;

            case 'stripe_payments_webhook_source_failed':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);

                $this->paymentsHelper->cancelOrCloseOrder($order);
                $this->addOrderCommentWithEmail($order, "Your order has been canceled because the payment authorization failed.");
                break;

            case 'stripe_payments_webhook_charge_succeeded':

                if (!empty($object['metadata']['Multishipping']))
                {
                    $orders = $this->webhooksHelper->loadOrderFromEvent($arrEvent, true);
                    $paymentIntentModel = $this->paymentIntentFactory->create();

                    foreach ($orders as $order)
                        $this->orderHelper->onMultishippingChargeSucceeded($order, $object);

                    return;
                }

                if ($this->wasCapturedFromAdmin($object))
                    break;

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                $hasSubscriptions = $this->paymentsHelper->hasSubscriptionsIn($order->getAllItems());

                $stripeInvoice = null;
                if (!empty($object['invoice']))
                {
                    $stripeInvoice = $this->config->getStripeClient()->invoices->retrieve($object['invoice'], []);
                    if ($stripeInvoice->billing_reason == "subscription_cycle" // A subscription has renewed
                        || $stripeInvoice->billing_reason == "subscription_update" // A trial subscription was manually ended
                        || $stripeInvoice->billing_reason == "subscription_threshold" // A billing threshold was reached
                    )
                    {
                        // We may receive a charge.succeeded event from a recurring subscription payment. In that case we want to create
                        // a new order for the new payment, rather than registering the charge against the original order.
                        break;
                    }
                }

                if (!$order->getEmailSent())
                {
                    $isPaymentElement = $order->getPayment()->getAdditionalInformation("client_side_confirmation")
                        || $order->getPayment()->getAdditionalInformation("payment_element");
                    $isStripeCheckout = $order->getPayment()->getAdditionalInformation("checkout_session_id");
                    $isTransactionPending = $order->getPayment()->getAdditionalInformation("is_transaction_pending");

                    if ($isStripeCheckout || ($isPaymentElement && $isTransactionPending)) // Magento will send the email for synchronous payment confirmations
                    {
                        $this->paymentsHelper->sendNewOrderEmailFor($order);
                    }
                }

                if (empty($object['payment_intent']))
                    throw new WebhookException("This charge was not created by a payment intent.");

                $transactionId = $object['payment_intent'];

                $payment = $order->getPayment();
                $payment->setTransactionId($transactionId)
                    ->setLastTransId($transactionId)
                    ->setIsTransactionPending(false)
                    ->setAdditionalInformation("is_transaction_pending", false) // this is persisted
                    ->setIsTransactionClosed(0)
                    ->setIsFraudDetected(false)
                    ->save();

                $amountCaptured = ($object["captured"] ? $object['amount_captured'] : 0);

                $this->orderHelper->onTransaction($order, $object, $transactionId);

                if ($amountCaptured > 0)
                {
                    // We intentionally do not pass $params in order to avoid multi-currency rounding errors.
                    // For example, if $order->getGrandTotal() == $16.2125, Stripe will charge $16.2100. If we
                    // invoice for $16.2100, then there will be an order total due for 0.0075 which will cause problems.
                    // $params = [
                    //     "amount" => $amountCaptured,
                    //     "currency" => $object['currency']
                    // ];
                    $this->paymentsHelper->invoiceOrder($order, $transactionId, \Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE, $params = null, true);
                }
                else if ($amountCaptured == 0) // Authorize Only mode
                {
                    if ($hasSubscriptions)
                    {
                        // If it has trial subscriptions, we want a Paid invoice which will partially refund
                        $this->paymentsHelper->invoiceOrder($order, $transactionId, \Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE, null, true);
                    }
                }

                if ($this->config->isStripeRadarEnabled() && !empty($object['outcome']['type']) && $object['outcome']['type'] == "manual_review")
                    $this->paymentsHelper->holdOrder($order);

                $order = $this->paymentsHelper->saveOrder($order);

                if (!empty($stripeInvoice) && $stripeInvoice->status == "paid")
                {
                    $this->creditmemoHelper->refundUnderchargedOrder($order, $stripeInvoice->amount_paid, $stripeInvoice->currency);
                }

                // Update the payment intents table, because the payment method was created after the order was placed
                $paymentIntentModel = $this->paymentIntentFactory->create()->load($object['payment_intent'], 'pi_id');
                $quoteId = $paymentIntentModel->getQuoteId();
                if ($quoteId == $order->getQuoteId())
                {
                    $paymentIntentModel->setPmId($object['payment_method']);
                    $paymentIntentModel->setOrderId($order->getId());
                    if (is_numeric($order->getCustomerId()) && $order->getCustomerId() > 0)
                        $paymentIntentModel->setCustomerId($order->getCustomerId());
                    $paymentIntentModel->save();
                }

                break;

            // Recurring subscription payments
            case 'stripe_payments_webhook_invoice_payment_succeeded':

                try
                {
                    $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                }
                catch (\StripeIntegration\Payments\Exception\SubscriptionUpdatedException $e)
                {
                    try
                    {
                        if ($object['billing_reason'] == "subscription_cycle")
                        {
                            return $this->recurringOrderHelper->createFromQuoteId($e->getQuoteId(), $object['id']);
                        }
                        else /* if ($object['billing_reason'] == "subscription_update") */
                        {
                            // At the very first subscription update (prorated or not), do not create a recurring order.
                            return;
                        }
                    }
                    catch (\Exception $e)
                    {
                        $this->webhooksHelper->sendRecurringOrderFailedEmail($arrEvent, $e);
                        throw $e;
                    }
                }

                if (empty($order->getPayment()))
                    throw new WebhookException("Order #%1 does not have any associated payment details.", $order->getIncrementId());

                $paymentMethod = $order->getPayment()->getMethod();
                $invoiceId = $stdEvent->data->object->id;
                $invoiceParams = [
                    'expand' => [
                        'lines.data.price.product',
                        'subscription',
                        'payment_intent'
                    ]
                ];
                $invoice = $this->config->getStripeClient()->invoices->retrieve($invoiceId, $invoiceParams);
                $isSubscriptionUpdateRequest = $this->getIsSubscriptionUpdateRequest($invoice, $invoice->payment_intent);

                if ($object['billing_reason'] == "subscription_update")
                {
                    // The event will arrive before the order is saved to the database. $order is likely the original order before
                    // the subscription was updated. So don't change any order state here. Use an after order saved observer instead.
                    return;
                }

                $isNewSubscriptionOrder = (!empty($object["billing_reason"]) && $object["billing_reason"] == "subscription_create");

                switch ($paymentMethod)
                {
                    case 'stripe_payments':
                    case 'stripe_payments_express':

                        $subscriptionId = $invoice->subscription->id;
                        $subscriptionModel = $this->subscriptionFactory->create()->load($subscriptionId, "subscription_id");
                        $subscriptionModel->initFrom($invoice->subscription, $order)->save();

                        $updateParams = [];
                        if (empty($invoice->subscription->default_payment_method) && !empty($invoice->payment_intent->payment_method))
                            $updateParams["default_payment_method"] = $invoice->payment_intent->payment_method;

                        if (empty($invoice->subscription->metadata->{"Order #"}))
                            $updateParams["metadata"] = ["Order #" => $order->getIncrementId()];

                        if (!empty($updateParams))
                            $this->config->getStripeClient()->subscriptions->update($subscriptionId, $updateParams);

                        if (!empty($invoice->payment_intent->id))
                        {
                            // The subscription description is not normally passed to the underlying payment intent
                            $this->config->getStripeClient()->paymentIntents->update($invoice->payment_intent->id, [
                                "description" => $this->paymentsHelper->getOrderDescription($order)
                            ]);
                        }

                        if (!$isNewSubscriptionOrder)
                        {
                            try
                            {
                                // This is a recurring payment, so create a brand new order based on the original one
                                $this->recurringOrderHelper->createFromInvoiceId($invoiceId);
                            }
                            catch (\Exception $e)
                            {
                                $this->webhooksHelper->sendRecurringOrderFailedEmail($arrEvent, $e);
                                throw $e;
                            }
                        }

                        break;

                    case 'stripe_payments_checkout':

                        if ($isNewSubscriptionOrder)
                        {
                            if (!empty($invoice->payment_intent))
                            {
                                // With Stripe Checkout, the Payment Intent description and metadata can be set only
                                // after the payment intent is confirmed and the subscription is created.
                                $quote = $this->paymentsHelper->loadQuoteById($order->getQuoteId());
                                $params = $this->paymentIntentFactory->create()->getParamsFrom($quote, $order, $invoice->payment_intent->payment_method);
                                $updateParams = $this->checkoutSessionHelper->getPaymentIntentUpdateParams($params, $invoice->payment_intent, $filter = ["description", "metadata"]);
                                $this->config->getStripeClient()->paymentIntents->update($invoice->payment_intent->id, $updateParams);
                                $invoice = $this->config->getStripeClient()->invoices->retrieve($invoiceId, $invoiceParams);
                            }
                            else if ($this->paymentsHelper->hasOnlyTrialSubscriptionsIn($order->getAllItems()))
                            {
                                // No charge.succeeded event will arrive, so ready the order for fulfillment here.
                                $order = $this->paymentsHelper->loadOrderById($order->getId()); // Refresh in case another event is mutating the order
                                if (!$order->getEmailSent())
                                {
                                    $this->paymentsHelper->sendNewOrderEmailFor($order, true);
                                }
                                if ($order->getInvoiceCollection()->getSize() < 1)
                                {
                                    $this->paymentsHelper->invoiceOrder($order, null, \Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
                                }
                                $this->paymentsHelper->setProcessingState($order, __("Trial subscription started."));
                                $this->paymentsHelper->saveOrder($order);
                            }

                            if ($invoice->status == "paid")
                            {
                                $this->creditmemoHelper->refundUnderchargedOrder($order, $invoice->amount_paid, $invoice->currency);
                            }
                        }
                        else // Is recurring subscription order
                        {
                            try
                            {
                                // This is a recurring payment, so create a brand new order based on the original one
                                $this->recurringOrderHelper->createFromSubscriptionItems($invoiceId);
                            }
                            catch (\Exception $e)
                            {
                                $this->webhooksHelper->sendRecurringOrderFailedEmail($arrEvent, $e);
                                throw $e;
                            }
                        }

                        break;

                    default:
                        # code...
                        break;
                }

                break;

            case 'stripe_payments_webhook_invoice_paid':

                $order = $this->webhooksHelper->loadOrderFromEvent($arrEvent);
                $paymentMethod = $order->getPayment()->getMethod();

                if ($paymentMethod != "stripe_payments_invoice")
                    break;

                $order->getPayment()->setLastTransId($object['payment_intent'])->save();

                foreach($order->getInvoiceCollection() as $invoice)
                {
                    $invoice->setTransactionId($object['payment_intent']);
                    $invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
                    $invoice->pay();
                    $this->paymentsHelper->saveInvoice($invoice);
                }

                $this->paymentsHelper->setProcessingState($order, __("The customer has paid the invoice for this order."));
                $this->paymentsHelper->saveOrder($order);

                break;

            case 'stripe_payments_webhook_invoice_payment_failed':
                //$this->paymentFailed($event);
                break;

            default:
                # code...
                break;
        }
    }

    public function addOrderCommentWithEmail($order, $comment)
    {
        if (is_string($comment))
            $comment = __($comment);

        try
        {
            $this->orderCommentSender->send($order, $notify = true, $comment);
        }
        catch (\Exception $e)
        {
            // Just ignore this case
        }

        try
        {
            $order->addStatusToHistory($status = false, $comment, $isCustomerNotified = true);
            $this->paymentsHelper->saveOrder($order);
        }
        catch (\Exception $e)
        {
            $this->webhooksHelper->log($e->getMessage(), $e);
        }
    }

    public function addOrderComment($order, $comment)
    {
        $order->addStatusToHistory($status = false, $comment, $isCustomerNotified = false);
        $this->paymentsHelper->saveOrder($order);
    }

    public function deduplicatePaymentMethod($object)
    {
        try
        {
            if (!empty($object['customer']))
            {
                $type = $object['type'];
                if (!empty($object[$type]['fingerprint']))
                {
                    $this->paymentsHelper->deduplicatePaymentMethod(
                        $object['customer'],
                        $object['id'],
                        $type,
                        $object[$type]['fingerprint'],
                        $this->config->getStripeClient()
                    );
                }
            }
        }
        catch (\Exception $e)
        {
            return false;
        }

        return true;
    }

    // When the customer upgrades/downgrades their subscription, the resulting payment intent does not have any metadata
    public function getIsSubscriptionUpdateRequest($invoice, $paymentIntent)
    {
        // The billing reason must be correct
        if ($invoice->billing_reason != "subscription_update")
            return false;

        // And the PI must not have previous order metadata
        if (!empty($paymentIntent->metadata->{"Order #"}))
            return false;

        return true;

        // These don't work.
        // // And in our DB, the last_updated timestamp should be within the last 6 hours
        // if (empty($invoice->subscription))
        //     return false;

        // if (is_string($invoice->subscription))
        //     $subscriptionId = $invoice->subscription;
        // else
        //     $subscriptionId = $invoice->subscription->id;

        // $subscriptionModel = $this->subscriptionFactory->create()->load($subscriptionId, "subscription_id");
        // if (!$subscriptionModel->getId())
        //     return false;

        // $lastUpdated = $subscriptionModel->getLastUpdated();
        // if (!$lastUpdated)
        //     return false;

        // $sixHours = 6 * 60 * 60;
        // if ((time() - $lastUpdated) > $sixHours)
        // {
        //     return false;
        // }

        // return true;
    }

    public function processTrialingSubscriptionOrder($order, $subscription)
    {
        if (is_string($subscription))
            $subscription = $this->config->getStripeClient()->subscriptions->retrieve($subscription);

        if ($subscription->status != "trialing")
        {
            // We are not interested in processing any other cases here.
            return;
        }

        // Trial subscriptions should still be fulfilled. A new order will be created when the trial ends.
        $state = \Magento\Sales\Model\Order::STATE_PROCESSING;
        $status = $order->getConfig()->getStateDefaultStatus($state);
        $comment = __("Your trial period for order #%1 has started.", $order->getIncrementId());
        $order->setState($state)->addStatusToHistory($status, $comment, $isCustomerNotified = true);

        if ($this->subscriptionsHelper->isZeroAmountOrder($order))
        {
            if (!$order->getEmailSent())
            {
                $this->paymentsHelper->sendNewOrderEmailFor($order, true);
            }

            // There will be no charge.succeeded event for trial subscription orders, so create the invoice here.
            // Then refund the amount that was not collected for the trial subscription. This is because when the
            // subscription activates, a new order will be created with a separate invoice.
            $this->paymentsHelper->invoiceOrder($order, null, \Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE);
            $baseRefundTotal = $order->getBaseGrandTotal();
            $creditmemo = $this->creditmemoHelper->refundOfflineOrderBaseAmount($order, $baseRefundTotal);
            $this->creditmemoHelper->save($creditmemo);
        }

        $this->paymentsHelper->saveOrder($order);
    }
}