# fluent-cart/1.5.1/app/Modules/PaymentMethods/StripeGateway/Webhook/Webhook.php

FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler, version 1.5.1. 312 lines.

- Page: https://pluginprobe.com/plugins/fluent-cart/1.5.1/code/app/Modules/PaymentMethods/StripeGateway/Webhook/Webhook.php
- Raw: https://pluginprobe.com/plugins/fluent-cart/1.5.1/raw/app/Modules/PaymentMethods/StripeGateway/Webhook/Webhook.php
- Modified: 2026-06-23T13:53:00+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-cart/1.5.1/code/app/Modules/PaymentMethods/StripeGateway/Webhook/Webhook.php#L10-L20`.

```php
<?php

namespace FluentCart\App\Modules\PaymentMethods\StripeGateway\Webhook;

use FluentCart\App\Helpers\Status;
use FluentCart\App\Helpers\CurrenciesHelper;
use FluentCart\App\Models\Order;
use FluentCart\App\Models\OrderTransaction;
use FluentCart\App\Models\Subscription;
use FluentCart\App\Modules\PaymentMethods\StripeGateway\API\API;
use FluentCart\App\Modules\PaymentMethods\StripeGateway\StripeHelper;
use FluentCart\App\Modules\Subscriptions\Services\SubscriptionService;
use FluentCart\Framework\Support\Arr;

class Webhook
{
    const WEBHOOK_ENDPOINT = '?fluent-cart=fct_payment_listener_ipn&method=stripe';

    public static function getURL(): string
    {
        return trailingslashit(site_url()) . self::WEBHOOK_ENDPOINT;
    }

    public static function getEvents(): array
    {
        return [
            'checkout.session.completed',
            'charge.refunded',
            'charge.refund.updated',
            'charge.succeeded',
            'invoice.paid',
            'customer.subscription.deleted',
            'customer.subscription.updated',
            'invoice.payment_failed'
        ];
    }

    public static function webhookInstruction(): array
    {
        $events = 'checkout.session.completed%2Ccharge.refunded%2Ccharge.refund.updated%2Ccharge.succeeded%2Cinvoice.paid%2Ccustomer.subscription.deleted%2Ccustomer.subscription.updated%2Cinvoice.payment_failed%2Ccharge.captured%2Ccharge.dispute.closed%2Ccharge.dispute.created%2Cinvoice_payment.paid%2Cpayment_intent.succeeded';
        
        $svg    = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10 6V8H5V19H16V14H18V20C18 20.5523 17.5523 21 17 21H4C3.44772 21 3 20.5523 3 20V7C3 6.44772 3.44772 6 4 6H10ZM21 3V11H19L18.9999 6.413L11.2071 14.2071L9.79289 12.7929L17.5849 5H13V3H21Z"></path></svg>';

        /* translators: %1$s: "Add endpoint" link with icon */
        $step = fn($class, $url) => \sprintf(
            '<p class="%s">%s</p>',
            $class,
            \sprintf(
                __('Click %1$s and paste the webhook URL above', 'fluent-cart'),
                \sprintf('<a href="%s" target="_blank">%s %s</a>', $url, __('Add endpoint', 'fluent-cart'), $svg)
            )
        );

        return [
            'title'       => __('Webhook URL', 'fluent-cart'),
            'webhook_url' => static::getURL(),
            'description' => __('You should configure your Stripe webhooks to get all updates of your payments remotely.', 'fluent-cart'),
            'steps'       => [
                'title' => __('How to configure?', 'fluent-cart'),
                'list'  => [
                    'live' => [
                        __('In your Stripe Dashboard, go to Developers → Webhooks', 'fluent-cart'),
                        $step('fct_hide_on_test', \sprintf('https://dashboard.stripe.com/webhooks/create?events=%s', $events)),
                    ],
                    'test' => [
                        __('In your Stripe Dashboard, go to Developers → Webhooks', 'fluent-cart'),
                        $step('fct_hide_on_live', \sprintf('https://dashboard.stripe.com/test/webhooks/create?events=%s', $events)),
                    ],
                ],
            ],
            'events' => [
                'title' => __('Select these events', 'fluent-cart'),
                'list'  => [
                    'checkout.session.completed',
                    'charge.refunded',
                    'charge.refund.updated',
                    'charge.succeeded',
                    'invoice.paid',
                    'invoice.payment_failed',
                    'customer.subscription.deleted',
                    'customer.subscription.updated',
                ],
            ],
        ];
    }

    public function processAndInsertOrderByEvent($event)
    {
        $eventType = $event->type;

        $metaDataEvents = [
            'invoice.paid', // Reviewed for subscription cycle
            'charge.refunded', // reviewed
            'charge.succeeded', // reviewed
            'charge.dispute.created',
            'charge.dispute.closed',
            'checkout.session.completed',
            'customer.subscription.deleted',
            'customer.subscription.updated',
        ];

        if (!in_array($eventType, $metaDataEvents)) {
            return false;
        }

        $vendorDataObject = $event->data->object;

        if ($eventType == 'invoice.paid') {
            //check if subscription billing_cycle invoice paid or failed
            $isSubscriptionCycle = $vendorDataObject->billing_reason === 'subscription_cycle';
            if ($isSubscriptionCycle) {
                if ($eventType === 'invoice.paid') {
                    $vendorDataObject = (new API())->getStripeObject('invoices/' . $vendorDataObject->id, ['expand' => ['payment_intent']]);
                    $createdOrder = $this->processSubscriptionRenewal($vendorDataObject);
                    if ($createdOrder) {
                        wp_send_json([
                            'message' => 'Subscription renewal processed successfully. Order ID: ' . $createdOrder->id,
                        ], 200);
                    }
                }

                return false;
            }
        }

        if ($eventType === 'charge.refunded' || $eventType === 'charge.succeeded') {
            $paymentIntent = $vendorDataObject->payment_intent;
            $orderTransaction = OrderTransaction::query()->where('vendor_charge_id', $paymentIntent)
                ->where('transaction_type', 'charge')
                ->first();

            if (!$orderTransaction) {
                $orderTransaction = apply_filters('fluent_cart/stripe/fallback_order_transaction', null, $vendorDataObject);
                if (!$orderTransaction || $orderTransaction instanceof OrderTransaction) {
                    $orderTransaction = null;
                }
            }

            if ($orderTransaction) {
                $order = Order::where('id', $orderTransaction->order_id)->first();
                $order->current_transaction = $orderTransaction;
                return $order;
            }
        }

        if ($eventType === 'customer.subscription.deleted' || $eventType === 'customer.subscription.updated') {

            $vendorSubscriptionId = $vendorDataObject->id;

            $subscription = Subscription::query()
                ->where('vendor_subscription_id', $vendorSubscriptionId)
                ->first();

            if ($subscription) {
                $order = Order::where('id', $subscription->parent_order_id)->first();
                if ($order) {
                    $order->current_subscription = $subscription;
                }
                return $order;
            }
        }

        if ($eventType === 'charge.dispute.created' || $eventType === 'charge.dispute.closed') {
            $paymentIntent = $vendorDataObject->payment_intent;
            $orderTransaction = OrderTransaction::query()->where('vendor_charge_id', $paymentIntent)
                ->first();

            if ($orderTransaction) {
                return $orderTransaction->order;
            }
            return null;
        }

        // Handle checkout.session.completed for hosted checkout
        if ($eventType === 'checkout.session.completed') {
            $sessionId = $vendorDataObject->id;
            return StripeHelper::validateBySession($sessionId);
        }

        $metaData = (array)$vendorDataObject->metadata;
        $orderHash = Arr::get($metaData, 'fct_ref_id', false);

        if ($orderHash) {
            return Order::query()->where('uuid', $orderHash)->first();
        }

        return null;
    }

    public function processSubscriptionRenewal($vendorInvoiceObject)
    {
        $subscription = null;
        $parentOrder = null;

        $vendorSubscriptionId = Arr::get($vendorInvoiceObject, 'subscription', null)
            ?: (Arr::get($vendorInvoiceObject, 'parent.subscription_details.subscription', null) ?? null);

        if ($vendorSubscriptionId) {
            $subscription = Subscription::query()->where('vendor_subscription_id', $vendorSubscriptionId)
                ->orderBy('id', 'DESC')
                ->first();
        }

        if ($subscription) {
            $parentOrder = Order::query()->where('id', $subscription->parent_order_id)->first();
        }

        if (!$parentOrder) {
            // let's try to find from the meta ref id
            $refId = Arr::get($vendorInvoiceObject, 'subscription_details.metadata.fct_ref_id', null);
            if ($refId) {
                $parentOrder = Order::query()->where('uuid', $refId)->first();
            }
        }

        if ($parentOrder && !$subscription) {
            $subscription = Subscription::query()
                ->where('parent_order_id', $parentOrder->id)
                ->orderBy('id', 'DESC')
                ->first();
        }

        if (!$parentOrder || !$subscription || $subscription->current_payment_method !== 'stripe') {
            fluent_cart_error_log('Stripe Webhook Error: Subscription Renewal - Order or Subscription not found.', 'Vendor Subscription ID: ' . $vendorSubscriptionId);
            return false; // this is not our order
        };
        $paymentIntent = Arr::get($vendorInvoiceObject, 'payment_intent', null);
        if (is_array($paymentIntent)) {
            $paymentIntentId = Arr::get($paymentIntent, 'id', null);
        } else {
            $paymentIntentId = $paymentIntent;
        }

        if ($paymentIntent) {
            $alreadyRecorded = OrderTransaction::query()
                ->where('subscription_id', $subscription->id)
                ->where('vendor_charge_id', $paymentIntentId)
                ->exists();

            if ($alreadyRecorded) {
                return null; // already recorded
            }
        }

        $amountPaid = Arr::get($vendorInvoiceObject, 'amount_paid', 0);
        $chargeCurrency = Arr::get($vendorInvoiceObject, 'currency', null);
        if ($chargeCurrency && CurrenciesHelper::isZeroDecimal($chargeCurrency)) {
            $amountPaid = $amountPaid * 100;
        }

        $transactionData = [
            'payment_method'   => 'stripe',
            'total'            => $amountPaid,
            'vendor_charge_id' => $paymentIntentId
        ];

        $paymentIntent = (new API())->getStripeObject('payment_intents/' . $paymentIntentId, [
            'expand' => ['latest_charge']
        ], $parentOrder->mode);

        if (!is_wp_error($paymentIntent)) {
            $transactionData['card_last_4'] = Arr::get($paymentIntent, 'latest_charge.payment_method_details.card.last4', '');
            $transactionData['card_brand'] = (string)Arr::get($paymentIntent, 'latest_charge.payment_method_details.card.brand', '');
            $transactionData['payment_method_type'] = (string)Arr::get($paymentIntent, 'latest_charge.payment_method_details.type', '');
        } else {
            $activePaymentMethod = $subscription->getMeta('active_payment_method', []);
            if (!$activePaymentMethod || !is_array($activePaymentMethod)) {
                $activePaymentMethod = [];
            }
            if ($activePaymentMethod) {
                $transactionData['card_last_4'] = Arr::get($activePaymentMethod, 'details.last_4');
                $transactionData['card_brand'] = (string)Arr::get($activePaymentMethod, 'details.brand');
                $transactionData['payment_method_type'] = (string)Arr::get($activePaymentMethod, 'details.type');
            }
        }

        $subscriptionUpdateData = array_filter([
            'current_payment_method' => 'stripe'
        ]);

        $stripeSubscription = (new API())->getStripeObject('subscriptions/' . $vendorSubscriptionId, [
            'expand' => ['latest_invoice']
        ], $parentOrder->mode);

        if (!is_wp_error($stripeSubscription)) {
            $subscriptionUpdateData = StripeHelper::getSubscriptionUpdateData($stripeSubscription, $subscription);
        }

        $createdTransaction = SubscriptionService::recordRenewalPayment($transactionData, $subscription, $subscriptionUpdateData);

        $subscription = Subscription::query()->find($subscription->id);

        if ($subscription && $subscription->status === Status::SUBSCRIPTION_COMPLETED) {
            if (!is_wp_error($stripeSubscription)) {
                if ($stripeSubscription['status'] === 'active') {
                    $deleted = (new API)->deleteStripeObject('subscriptions/' . $vendorSubscriptionId, [], $parentOrder->mode);
                    if (is_wp_error($deleted)) {
                        fluent_cart_error_log('Stripe Subscription Deletion Error. Subscription ID: ' . $subscription->id, $deleted->get_error_message());
                    }
                }
            }
        }

        if ($createdTransaction) {
            return $createdTransaction->order;
        }

        return null;
    }

}

```
