Drupal Commerce handles money through two separate conversations: your site talking out to a gateway such as Stripe, PayPal, or Braintree, and the gateway talking back to your site through an Instant Payment Notification or webhook. When a payment appears to hang at checkout, completes but never marks the order as paid, or silently drops subscription renewals, the break is almost always in one of those two directions. On shared hosting the usual suspects are outbound cURL being blocked or using an outdated TLS stack, PHP running out of time or memory mid-request, a webhook URL that your own security rules are rejecting, or API credentials saved against the wrong environment. None of these require root to fix, but they do require knowing exactly where Drupal stores its clues.
Before changing anything, confirm which workflow you actually have. Drupal core does not process payments on its own. The Commerce module (Commerce 2.x on Drupal 9/10) plus a payment gateway submodule is what defines a checkout and a commerce_payment entity. If your site only uses Webform with a payment handler, or a contributed donation module, the admin paths differ but the underlying cURL and webhook mechanics are identical. Everything below assumes a standard Commerce installation reachable at /admin/commerce.
Reading the logs and reproducing the checkout failure
Drupal records gateway interactions in two places, and you need both. The first is the database log at Administration > Reports > Recent log messages (/admin/reports/dblog). Filter the Type column for commerce_payment, commerce_order, and the gateway machine name such as stripe or commerce_paypal. A failed capture typically logs an exception with the gateway's raw response, which tells you whether the request even left the server. The second source is the PHP error log written by your account. On LiteSpeed under cPanel this is usually error_log in the document root of the domain, or the per-domain log you open through cPanel > Metrics > Errors. In DirectAdmin, check File Manager for error_log inside public_html or the subfolder running the site. Fatal errors from a gateway SDK that never reach the Drupal logger surface only here.
Reproduce the failure with the database log open in a second tab. Place a test order using the gateway's sandbox mode and watch which entry appears last. If the final log line is a cURL error like Failed to connect or Could not resolve host, the problem is outbound connectivity. If the last line is a timeout or a white screen with nothing logged, the request started but PHP killed it. If the order completes locally but the gateway dashboard shows the charge succeeded while Drupal still says Pending, the outbound call worked but the webhook that confirms capture never arrived. That three-way split determines which section below applies.
Fixing outbound cURL, TLS, and the PHP environment
Payment SDKs require a modern PHP build with the cURL and OpenSSL extensions enabled and a TLS 1.2 or 1.3 handshake. Stripe and PayPal both reject TLS 1.0/1.1 outright, so an old PHP version is a common silent killer. Set the version through cPanel > Software > Select PHP Version (the MultiPHP / PHP Selector interface) or DirectAdmin > Account Manager > PHP Version Selector. Choose PHP 8.1 or newer for current Commerce releases, then tick the extensions the gateway needs: curl, openssl, json, mbstring, and bcmath. Save and reload the checkout. In the same PHP Selector screen, open the options editor and raise the limits that cut off long gateway calls:
max_execution_time = 120
memory_limit = 256M
default_socket_timeout = 60If the PHP Selector will not expose a setting, place a .user.ini file in your document root with the same directives, one per line. LiteSpeed reads it per directory, and changes apply after the configured refresh interval (usually 300 seconds) or after you touch a PHP file. Do not attempt to edit php.ini in /etc or use SET GLOBAL; those are off-limits on shared accounts and unnecessary here.
An outdated CA bundle is the other frequent cause of TLS failures. When the log shows SSL certificate problem: unable to get local issuer certificate, cURL cannot verify the gateway's certificate chain. Never disable verification as a fix. Instead confirm your PHP version uses a current bundle, and if the host's bundle is stale, point your gateway library at a fresh one. If the gateway module exposes an advanced setting for a CA path, upload a current cacert.pem to a private folder like ../private/cacert.pem (outside the web root) and reference it there. For Stripe, the module's own bundled certificate usually resolves this once PHP and OpenSSL are current. After any change, clear Drupal's cache at /admin/config/development/performance so cached service containers pick up the new environment.
Ensuring webhooks and payment callbacks reach Drupal
Gateways post confirmations to a fixed endpoint. Commerce exposes routes such as /payment/notify/{commerce_payment_gateway} for IPN and gateway-specific paths like /commerce-stripe/webhook or the PayPal IPN listener. The gateway only knows the URL you register in its dashboard, so an incorrect or http:// address, or a trailing slash mismatch, produces delivery failures the gateway logs but Drupal never sees. Copy the exact callback URL from the gateway configuration page at /admin/commerce/config/payment-gateways and paste it verbatim into the gateway's dashboard, always over HTTPS.
The more subtle problem is your own server blocking the incoming POST. Security rules in .htaccess, a WAF, bot filters, or a hotlink/referrer check frequently reject requests that carry no browser user agent and no cookies, which is exactly what a webhook looks like. If you have added hardening rules, exempt the callback path explicitly:
<IfModule mod_rewrite.c>
RewriteEngine On
# Allow gateway webhooks through before any blocking rules
RewriteCond %{REQUEST_URI} ^/(payment/notify|commerce-stripe/webhook) [NC]
RewriteRule ^ - [L]
</IfModule>Place that block above any rules that deny empty user agents or enforce referrers. If you run basic authentication or an IP allow-list over the admin area, make sure it does not cover the payment paths, since the gateway cannot authenticate. The same discipline applies to broader access controls; the approach mirrors what we describe in Joomla security hardening on shared hosting, where HTTP-level rules must leave machine endpoints reachable. Test the live endpoint by sending the gateway's built-in test event from its dashboard, then check /admin/reports/dblog for an incoming notification entry. A 403 or 406 in your domain's access log at cPanel > Metrics > Raw Access confirms the request arrived but was refused, pointing straight back to a rewrite or WAF rule.
Validating API credentials and auditing transactions
Mismatched credentials are the quietest failure of all because the request succeeds against the wrong account. Each gateway edit form at /admin/commerce/config/payment-gateways/manage/{gateway} has a Mode toggle for Test versus Live and separate key fields for each. Confirm that live secret and publishable keys are not sitting in test fields, and that the webhook signing secret matches the mode you are running. Stripe signs each webhook with a secret tied to a specific endpoint; if you recreate the endpoint in the dashboard, you must copy the new signing secret back into Drupal or every notification fails signature verification and gets rejected as a 400. When credentials live in settings.php via environment overrides, edit that file only through File Manager in your own account space, never a server-wide config.
Finish by reconciling what the gateway recorded against what Drupal stored. The payment list at /admin/commerce/orders and the payments tab on each order show the commerce_payment entity state, the remote ID, and the remote state returned by the gateway. A healthy capture has a remote ID matching the transaction in the gateway dashboard and a state of Completed. Orders stuck on Pending with a valid remote ID almost always indicate the webhook never landed, sending you back to the delivery section rather than the credentials. Keep the database log retention generous under /admin/config/development/logging while you troubleshoot, then lower it afterward so the watchdog table does not bloat. With PHP current, TLS negotiating cleanly, callback paths exempted from blocking, and credentials matched to the correct mode, the full round trip from checkout to confirmed payment completes reliably on a standard shared account.