craftcms / commerce

Fully integrated ecommerce for Craft CMS.
https://craftcms.com/commerce
Other
218 stars 170 forks source link

[5.x]: TypeError when completing payments with Stripe Gateway in Craft Commerce 5.1.1 #3683

Closed romainpoirier closed 5 days ago

romainpoirier commented 6 days ago

What happened?

Description

I am no longer able to process transactions with Craft Commerce using the Stripe gateway.

The issue occurs with both my custom payment form and the provided boilerplate form. Everything was working fine until now. I have tried downgrading to previous versions of Commerce and Stripe, but the issue persists.

Here are the relevant package versions I'm using:

The error I am encountering is as follows:

TypeError: craft\commerce\services\Transactions::getTransactionByHash(): Argument #1 ($hash) must be of type string, null given, called in /var/www/html/vendor/craftcms/commerce/src/controllers/PaymentsController.php on line 537 and defined in /var/www/html/vendor/craftcms/commerce/src/services/Transactions.php:349
Stack trace:
#0 /var/www/html/vendor/craftcms/commerce/src/controllers/PaymentsController.php(537): craft\commerce\services\Transactions->getTransactionByHash(NULL)
#1 [internal function]: craft\commerce\controllers\PaymentsController->actionCompletePayment()
#2 /var/www/html/vendor/yiisoft/yii2/base/InlineAction.php(57): call_user_func_array(Array, Array)
#3 /var/www/html/vendor/yiisoft/yii2/base/Controller.php(178): yii\base\InlineAction->runWithParams(Array)
#4 /var/www/html/vendor/yiisoft/yii2/base/Module.php(552): yii\base\Controller->runAction('complete-paymen...', Array)
#5 /var/www/html/vendor/craftcms/cms/src/web/Application.php(350): yii\base\Module->runAction('commerce/paymen...', Array)
#6 /var/www/html/vendor/craftcms/cms/src/web/Application.php(649): craft\web\Application->runAction('commerce/paymen...', Array)
#7 /var/www/html/vendor/craftcms/cms/src/web/Application.php(312): craft\web\Application->_processActionRequest(Object(craft\web\Request))
#8 /var/www/html/vendor/yiisoft/yii2/base/Application.php(384): craft\web\Application->handleRequest(Object(craft\web\Request))
#9 /var/www/html/htdocs/index.php(12): yii\base\Application->run()
#10 {main}

Steps to reproduce

  1. Attempt to complete a transaction using the Stripe gateway in Craft Commerce.
  2. Observe the error during the payment process.

Expected behavior

The transaction should complete successfully, and the payment should be processed using the Stripe gateway.

Actual behavior

The transaction fails, and a TypeError is thrown, indicating that a null value is being passed where a string is expected.

Additional information

Craft CMS version

5.4.4

Craft Commerce version

5.1.1

PHP version

8.2.20

Operating system and version

No response

Database type and version

No response

Image driver and version

No response

Installed plugins and versions

-

linear[bot] commented 6 days ago

PT-2157 [5.x]: TypeError when completing payments with Stripe Gateway in Craft Commerce 5.1.1

lukeholder commented 6 days ago

@romainpoirier I have just tried commerce-stripe 5.0.4.3 and Craft Commerce 5.1.1 and I was able to make a payment with Stripe elements form (default form) as well as stripe checkout offsite form type. I didn't get your error.

It looks like your complete payment controller action URL is missing the transactionHash=xxx param.

This URL is generated by Craft Commerce so it shouldn't be missing. Do you have any redirects or URI rewriting changes that could be stripping the URL parameters? This would be my first guess, that something changed with your environment.

This is why reverting to the previous version of commerce-stripe would still not work.

Did anything else change with your hosting environment / webserver / other URL-related plugins / cloudflare?

Let us know. Thanks.

romainpoirier commented 5 days ago

Do you have any redirects or URI rewriting changes that could be stripping the URL parameters?

Yes, you guessed correctly! I indeed have a service that forces the site's language based on the user's preferences:

<?php

namespace modules\custom\services;

use Craft;
use yii\base\Component;
use yii\base\Event;
use craft\web\Application;

/**
 * Redirect service
 */
class RedirectService extends Component
{
    /**
     * Register the event handler for the redirect action based on browser language
     */
    public function registerRedirectEvent(): void
    {
        // Attach an event before Craft processes a request
        Event::on(
            Application::class,
            Application::EVENT_BEFORE_REQUEST,
            function () {
                $this->handleRedirect();
            }
        );
    }

    /**
     * Handle the redirect based on the browser's preferred language
     */
    public function handleRedirect(): void
    {
        $request = Craft::$app->getRequest();

        // Verify it's a frontend request and not an AJAX request
        if ($request->getIsSiteRequest() && !$request->getIsAjax()) {
            // Get the browser language
            $browserLanguage = substr($request->getPreferredLanguage(['en', 'fr', 'de']), 0, 2);

            // Get the current site's language
            $currentLanguage = Craft::$app->getSites()->currentSite->language;

            // Get the path of the current request
            $currentUrl = $request->getFullPath();

            // Check if the URL already contains a language prefix
            $hasLanguagePrefix = preg_match('#^(en|fr|de)(/|$)#', $currentUrl);

            // Redirect if the browser language does not match the current site's language
            // and the URL does not already contain a language prefix
            if ($browserLanguage !== $currentLanguage && !$hasLanguagePrefix) {
                // Build the redirect URL with the browser language as prefix
                $url = '/' . $browserLanguage . '/' . $currentUrl;

                // Default to English if no match is found for the browser language
                if (!in_array($browserLanguage, ['en', 'fr', 'de'])) {
                    $url = '/en/' . $currentUrl;
                }

                // Perform the redirect
                Craft::$app->getResponse()->redirect($url)->send();
                Craft::$app->end();
            }
        }
    }
}

What extra exclusion can I add to ensure this doesn't interfere with Craft Commerce or any other plugin that might perform redirections?

lukeholder commented 5 days ago

@romainpoirier

You should probably be looking to skip the custom redirect code for controller actions by checking $this->request->getIsActionRequest() or interrogating the $this->request->getActionSegments() for actions you don't want to have your redirects affect.

Hope that helps.