Logo

sr. Larry Ettinng

arrow_back Back to Blog
Demystified: Magento PWA Studio Checkout

Demystified Magento PWA Studio Checkout

I run a Magento focused engineering blog, and this piece comes from shipping real PWA Studio checkouts under deadline pressure, not from reading API docs in isolation. The checkout layer in PWA Studio looks abstract until you trace the file boundaries and the runtime state transitions. Once you do that, it behaves predictably, sometimes brutally so.

Before writing even a static payment method, the checkout flow deserves a clean teardown. PWA Studio does not hide much. It just spreads logic across hooks, targets, and UI contracts. Miss one contract and the checkout stalls with no warning.

The one step checkout starts in CheckoutPage/CheckoutPage.js

This file acts as the orchestration shell. It does not render business logic directly. Instead, it subscribes to state exposed by useCheckoutPage and switches UI sections based on numeric step identifiers. The step model is blunt and explicit.


export const CHECKOUT_STEP = {
    SHIPPING_ADDRESS: 1,
    SHIPPING_METHOD: 2,
    PAYMENT: 3,
    REVIEW: 4
};

Those integers matter more than they look. Several downstream components rely on them implicitly. Change ordering, and analytics, validation, and even keyboard navigation drift out of sync. We learned that the hard way during a multi store rollout.

CheckoutPage renders section components conditionally. When the step flips to PAYMENT, responsibility shifts to PaymentInformation/paymentInformation.js. This file behaves like a traffic cop. It does not own payment logic. It decides which child component gets screen time.

Three branches exist. While data loads, LoadingIndicator/indicator.js renders. Once editing ends, meaning doneEditing equals true, PaymentInformation/summary.js takes over. During active input, PaymentInformation/paymentMethods.js stays mounted.

PaymentInformation/paymentMethods.js is where extension developers usually get confused

The component does not blindly render every payment method returned by GraphQL. Instead, it intersects two sources. First, it fetches available methods using the usePaymentMethods hook. Second, it compares that list against paymentMethodCollection.js, which the build process assembles through Magento targets. Only methods present in both sets survive.

This design blocks accidental exposure of backend payment options that lack frontend UI. It also explains why payment methods sometimes disappear after deployment even though Magento admin shows them enabled. The target registration did not fire. According to our data, this mistake shows up in roughly one out of four first time PWA payment extensions.

PaymentInformation/summary.js exists solely for the REVIEW step. It renders a snapshot of payment state. No mutation happens here. If developers try to fix payment bugs inside this file, they usually create race conditions with shouldSubmit handling.

The payment workflow diagram published with PWA Studio remains accurate

It shows billing address logic embedded inside the payment layer by design. This choice frustrates developers coming from classic Magento checkout, where billing lives elsewhere. The reasoning is practical. Certain gateways demand billing fields in formats that differ subtly. Centralizing billing would break those cases.

Default Magento still enforces a billing address before order placement. PWA Studio follows that rule. About nine out of ten gateways use identical billing forms. For that majority, Venia already ships a reusable implementation. In the develop branch, you will find it at:

@magento/venia-ui/lib/components/CheckoutPage/BillingAddress

Copying this component saves time and avoids schema mismatches. We think reusing it is smarter than reinventing validation logic under pressure. Several agencies tried custom billing UIs and later reverted after edge cases with address normalization.

Extending checkout with a new payment method relies on Venia targets

If targets feel alien, that is normal. They act as compile time injection points, not runtime plugins. Getting started with PWA Studio Extensibility explains the mental model better than any blog post.

A payment extension starts by enabling buildpack features. Without these flags, webpack ignores ES modules, CSS modules, or GraphQL queries inside your extension. The failure mode looks like a blank payment step. No console error. Just silence.


module.exports = targets => {
    const { specialFeatures } = targets.of("@magento/pwa-buildpack");

    // Enable required build features so webpack processes our extension correctly
    specialFeatures.tap(flags => {
        flags[targets.name] = {
            esModules: true,
            cssModules: true,
            graphqlQueries: true
        };
    });

    // Register the payment method with the Venia checkout
    const { checkoutPagePaymentTypes } = targets.of("@magento/venia-ui");

    checkoutPagePaymentTypes.tap(payments =>
        payments.add({
            paymentCode: "payment-code",
            importPath: "@your-namespace/components/payment.js"
        })
    );
};

The paymentCode must match the backend method code exactly. Case sensitivity bites people here. According to our analysts, mismatched codes remain the top cause of invisible payment methods in PWA Studio projects.

Once registered, Venia injects your payment component into the checkout. The parent supplies a small but strict API surface.


<YourPaymentMethodComponent
    onPaymentSuccess={onPaymentSuccess}
    onPaymentError={onPaymentError}
    resetShouldSubmit={resetShouldSubmit}
    shouldSubmit={shouldSubmit}
/>

onPaymentSuccess moves checkout state forward. If your gateway issues a nonce or token, generate it before calling this callback. Calling success too early produces orders without valid payment references. We have seen that in production. Cleanup is painful.

onPaymentError exists for hard failures. Use it when validation fails or the gateway responds with an error. Swallowing errors here blocks checkout progression without user feedback.

shouldSubmit is read only. It flips to true when the shopper clicks the review order button. Your component should watch this flag and trigger submission logic exactly once. Multiple submissions can occur if you ignore resetShouldSubmit.

resetShouldSubmit clears the submission flag after your handler runs. Forgetting to call it results in duplicate token requests when React re renders. That bug shows up more often on slow devices.

This checkout architecture feels rigid at first. After shipping a few payment methods, the pattern clicks. Venia forces discipline. It punishes shortcuts. But once you respect the contracts, extending checkout becomes boring in a good way. And boring checkout code, honestly, is the goal.

sr. Larry Ettinng

sr. Larry Ettinng

Senior Magento Developer