Skip to content

Block checkout: ReferenceError when no payment provider is selected, surfaced as generic "There was a problem with your payment option." #241

Description

@ollisaari

Summary

On block checkout, if the customer selects Paytrail as the payment method but does not pick an individual payment provider (bank/card/mobile), clicking Place order fails with WooCommerce's generic error "There was a problem with your payment option." instead of the plugin's own, descriptive message.

The cause is a ReferenceError in assets/js/containers/paytrail-container.jsx: defaultProvider is referenced in the onPaymentSetup callback but declared inside an if block, so it is out of scope whenever provider groups exist (the normal case).

Environment

  • Paytrail for WooCommerce: 2.7.0 (also present on master as of today)
  • WooCommerce: 10.9.4
  • WordPress: 7.0.2
  • PHP: 8.3
  • Checkout: block checkout (wp:woocommerce/checkout)
  • Provider groups configured normally (bank / mobile / card / invoice — settings.groups is non-empty)

Steps to reproduce

  1. Use the block checkout with Paytrail enabled and the provider list visible (groups rendered as accordion).
  2. Add a product to the cart, go to checkout, fill in the required fields.
  3. Select Paytrail as the payment method, but do not open a group or select an individual provider.
  4. Click Place order.

Expected

A clear message telling the customer to choose a payment provider — the plugin already has exactly this string in src/Gateway.php:

if ( ! $payment_provider && ! $is_token_payment ) {
    wc_add_notice(
        __( 'The payment provider was not chosen.', 'paytrail-for-woocommerce' ),
        'error'
    );

Actual

WooCommerce Blocks shows its generic fallback "There was a problem with your payment option." and the browser console logs ReferenceError: defaultProvider is not defined.

The request never reaches the server, so the PHP-side validation above never runs and its message is never seen.

Root cause

assets/js/containers/paytrail-container.jsx:

let cleanProvider = activeProvider.replace(/-\d+$/, '');

if (!settings.groups || settings.groups.length === 0) {
    const defaultProvider = 'paytrail';   // <-- block-scoped to this if
    if (!cleanProvider) {
        cleanProvider = defaultProvider;
    }
}

// ...

useEffect(() => {
    const paymentSetup = onPaymentSetup(async () => {
        return {
            type: emitResponse.responseTypes.SUCCESS,
            meta: { paymentMethodData: { payment_provider: cleanProvider || defaultProvider } }
        }
    });
    return () => { paymentSetup(); };
}, [...]);

When settings.groups is non-empty and the customer has not selected a provider, cleanProvider is an empty string, so the || falls through to defaultProvider, which is not defined in that scope. The thrown ReferenceError is caught by WooCommerce Blocks' payment setup handling and replaced with the generic notice.

Impact

This is the default state of every block checkout session — the customer has to actively notice the accordion and pick a provider before the button works. When they don't, they get an error that does not tell them what to do. Since the error appears in the payment section's notice area and the page is not scrolled to it, on mobile the message is typically off-screen entirely, so the customer only sees that nothing happens. In our case this was a measurable drop-off point in checkout.

Suggested fix

Two independent changes:

  1. Fix the scope bug so the reference resolves:
const defaultProvider = 'paytrail';
let cleanProvider = activeProvider.replace(/-\d+$/, '');

if (!settings.groups || settings.groups.length === 0) {
    if (!cleanProvider) {
        cleanProvider = defaultProvider;
    }
}
  1. Return a proper error instead of silently falling back. Note that with fix 1 alone, an unselected provider would submit payment_provider: 'paytrail', which is probably not desirable when groups exist. Emitting an error from the observer would surface a useful message directly in the payment section:
if (settings.groups?.length > 0 && !cleanProvider) {
    return {
        type: emitResponse.responseTypes.ERROR,
        message: __( 'The payment provider was not chosen.', 'paytrail-for-woocommerce' ),
    };
}

This reuses the string that already exists on the PHP side, so no new translations are needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions