Your shopper wanted it, just not all at once: building the Float Payments module for Magento

Home / Blog / Your shopper wanted it, just not all at once: building the Float Payments module for Magento


A shopper is standing at your checkout with a full cart and one hesitation: not "do I want this," but "do I want this this month." That's the moment you lose. Float answers it by letting them split the purchase into interest-free monthly instalments on the credit card already in their wallet, no application, no credit check, while you get paid in full upfront. We built the Magento module that puts that choice in your checkout, and this is the honest account of what it took, because the interesting parts of a payment integration are never the happy path.

What your shopper actually gets

Float is card-linked instalments. Your customer picks Float at checkout, verifies with a one-time pin, and the amount is split across interest-free, fee-free monthly instalments against the available limit on their existing Visa or Mastercard. There's nothing to sign up for, no new credit line, and they keep earning their usual rewards points. It's their own credit, rearranged.

Your side of that trade is the part that matters to your P&L: you receive the full amount upfront. You aren't financing anything and you aren't carrying the risk. Float reports merchants seeing meaningfully higher average order values, which is the expected shape of the thing. A price that reads as "a lot right now" reads differently as a monthly number, and the cart that used to get abandoned at the total gets completed instead.

That's the offer. Your job was never to build the financing. Your job is to make sure the option shows up, in the right places, without breaking anything. That's what the module is for.

The option has to appear before checkout, not at it

A shopper who reaches your checkout has already decided what they can afford. Telling them about instalments at the payment step is too late to change what's in the cart. So the module puts a payment plan preview where the decision actually happens:

| Where | What your shopper sees | You control it with |
| --- | --- | --- |
| Product page | Instalment preview under the price | Show Payment Plan Preview on Product Page |
| Product cards | Float badge in listings and grids | Show Payment Plan Preview on Product Cards |
| Checkout | Float as a payment method | Enable Solution, plus min and max cart amount |

One detail we care about: that preview isn't a hardcoded "up to 24 months" marketing number. The block reads your own Float merchant configuration, walks the terms in your rules, and renders the real maximum available to you. If your terms change on Float's side, your product pages follow. Nobody has to remember to update a static string, which means nobody forgets to and quietly misleads a customer for six months.

And both previews are independently switchable per store, because the merchandiser who wants it on product pages but not cluttering a 48-item category grid is a real person with a real opinion.

The hard part: who creates the order, and when?

Here's the problem at the center of every redirect-based payment method. Your shopper leaves your site, does something on the provider's page, and comes back. Or doesn't come back. Or closes the laptop mid-flow. Or pays successfully and then their train goes into a tunnel. Somewhere in that gap you have to decide when a Magento order exists.

We built it the tidy way first: create the order when the success webhook arrives. Only orders that got paid ever become orders, so your sales data stays clean. That's version 1.0.2, and it was wrong.

It's wrong because between the redirect and the webhook, the purchase exists nowhere in your system. Stock isn't reserved. The increment ID doesn't exist yet. If anything at all goes sideways in that window, the customer's money moved and you have no order to attach it to. Reconciling that by hand is somebody's whole afternoon, and it's always the afternoon of your busiest day.

So in 1.0.5 we inverted it: the Magento order is created before the redirect, in `pending_payment`, with the quote reserving its order ID up front. Now every attempt is a real record you can find, and the webhook's job shrinks to moving state on an order that already exists.

That inversion creates one new problem, and it's a much better problem to have: abandoned pending orders. So the module sweeps them:

$orderCollection->addFieldToFilter('state', ['in' => [Order::STATE_NEW, Order::STATE_PENDING_PAYMENT]]);
$orderCollection->addFieldToFilter('method', ['eq' => FloatHelper::METHOD_CODE]);
$orderCollection->addFieldToFilter('created_at', ['lteq' => $date]); // 24 hours ago

A cron cancels Float orders left unpaid for a day, scoped strictly to this payment method so it can never touch an order that isn't ours. Cancelled rather than deleted, because "this customer tried and didn't finish" is information you might want, and because silently deleting sales records is how you lose an audit.

Trading a clean order table for a complete one is the right trade every time. You can filter a report. You cannot recover a payment with no order behind it.

Trusting a webhook you didn't send

The notify endpoint is the module's most dangerous surface. It's a public URL, it moves money, and anyone can POST to it. Four guardrails, in order:

  • It validates the response before acting on it. If the payload doesn't check out against the order, the order gets cancelled and the failure is logged. It does not get the benefit of the doubt.
  • It only accepts orders in a state that makes sense. Anything not `pending_payment` or `new` is logged and ignored, which makes the endpoint idempotent for free. A provider retrying a webhook three times is normal behavior, not a reason to invoice a customer three times.
  • Emails wait for the invoice. Version 1.0.6 fixed this: the confirmation only sends once payment actually succeeded and an invoice exists, guarded against double-sends. Your customer never gets a receipt for a payment that failed.
  • Everything lands in one log at `var/log/float.log`, debug and error both, with debug switchable from the admin. When something does go wrong at 2am, there's one file to read.

Built with Magento's payment pattern, not around it

It would have been faster to write a controller that curls Float and stuffs data into the order. It also would have been the module that breaks on your next upgrade.

Instead it's a proper gateway integration on Magento's own payment framework: a facade virtualType, a command pool, a validator pool.

<virtualType name="FloatValidatorPool" type="Magento\Payment\Gateway\Validator\ValidatorPool">
    <arguments>
        <argument name="validators" xsi:type="array">
            <item name="currency" xsi:type="string">X2Y\FloatPayments\Gateway\Validator\Currency</item>
            <item name="country" xsi:type="string">X2Y\FloatPayments\Gateway\Validator\Country</item>
        </argument>
    </arguments>
</virtualType>

Why you should care about an architecture decision in a module you'll never open: it's the difference between an integration that survives Magento upgrades and one that becomes your problem. Country and currency eligibility are validators, so Float appears only where you've configured it to. Availability is an observer checking cart minimum and maximum, so a R50 cart doesn't offer instalments. Config lives in the standard Payment Methods section, scoped per website, so a multi-store operator configures it the way they configure everything else. No custom admin screen to learn. Nothing clever enough to be fragile.

The unglamorous work is the whole product

Scroll the changelog and there's no headline feature. There's a long list of the specific ways a payment method can quietly fail one segment of your customers:

  • Guest checkout was broken (1.0.9). Guests carry a masked quote ID, not a real one, so the module now resolves it when there's no logged-in customer. Before that fix, a chunk of your buyers hit a payment error. They don't file a bug report. They just leave.
  • Multi-website support (1.0.4, hardened in 1.0.8) and per-store config scope. Different brands, different Float credentials, different terms.
  • PHP 8.3 and Magento 2.4.6 through 2.4.7, so the payment module is never the reason you can't upgrade.
  • Order status is configurable, because your fulfilment team's workflow is yours and not ours to dictate.
  • Auth tokens self-heal. Credentials are exchanged through an SPI and cached; re-saving the payment config refreshes them. No expiry to babysit.
  • Unit tests on the paths that touch money: the notify controller, order creation, the cancel cron, the login SPI, availability. Not for a coverage badge. So that the thing which breaks in a refactor is a test, not a customer's checkout.

None of that is exciting. All of it is why you can install this and then stop thinking about it, which is the only real compliment a payment module can receive.

Your move

You did the hard part already. You built the catalogue, earned the traffic, wrote the product copy, and got a shopper to the checkout with a full cart. All that's left is making sure the price on the screen isn't the reason they walk, and that's a solved problem now.

If you're running Magento or Adobe Commerce and want Float in your checkout, we build and maintain this module, and we can have you live without your team learning a payment framework to get there. If you're already running it and want the plan preview tuned, the flow adapted, or a stubborn edge case chased down, that's our regular work too.

Tell us about your store, or read more about how we build payment and platform integrations.


Written by X2Y.DEV
Float Magento Adobe Commerce Payments BNPL
Back to Blog

0%