WooCommerce Payment Gateway Engineering
Custom payment gateways for WooCommerce stores that keep card data off WordPress and orders in sync
Project Overview
Payment gateway engineering for WooCommerce stores is a recurring practice for us rather than a single project. Stores come to us when an off-the-shelf plugin does not fit: they need card data kept out of WordPress, payments taken on a separate service, payments spread across several receiving accounts, crypto accepted, or a vendor plugin repaired when it breaks at checkout. This case study brings together a set of gateways we have built. The set covers a tokenised card vault, an off-site Laravel payment service, a direct-pay gateway that rotates between receiving accounts, a crypto checkout confirmed by hand, and a root-cause fix for a vendor crypto widget. The direct-pay and crypto gateways work in both the classic checkout and WooCommerce Checkout Blocks.
Skills Applied
The Challenge
A WooCommerce store that handles card numbers inside WordPress puts its whole site, including every plugin and theme, in scope for card-data risk. Some merchants need a different setup: the card form hosted somewhere else, the charge taken by a separate application, and WordPress told only the result. The link between the two systems has to be tamper-proof, or anyone could mark an order as paid. Other merchants take direct transfers into several receiving accounts, and each account has its own daily limit, so orders have to be spread across them automatically. Crypto buyers expect a checkout that waits for the payment to be confirmed instead of pretending it has already cleared. On top of this, WooCommerce now has two checkouts, the classic shortcode and the block-based checkout, and a gateway that supports only one of them quietly disappears from the other. Vendor plugins bring their own failures: a crypto payment widget kept failing for guest customers on one store but worked on another.
Our Solution
We build each gateway as a focused WooCommerce plugin. For card payments we use two patterns. The first is a vault connector: a hosted card-entry iframe that sends a token back to the checkout through postMessage, and the checkout accepts it only from the configured vault origin. WordPress stores just the token and masked details (brand, last four digits, expiry). A REST webhook, verified with HMAC-SHA256 and a constant-time comparison, applies token updates and invalidations to the matching orders. The second is an off-site payment bridge. The gateway posts the order to a Laravel 10 service, and the request is signed with HMAC-SHA256 over the exact raw JSON body. The customer enters card details on the Laravel page inside a full-page overlay, and Laravel charges through Stripe behind a payment-gateway contract and factory. It then sends a signed callback, and WordPress verifies the signature before it completes the order. For direct transfers we built a gateway that keeps a table of receiving accounts, each with its own daily limit. At checkout it picks a random active account that still has capacity, records it on the order and fills in customer instructions from editable templates. For crypto we built an offline gateway that reserves stock, puts the order on hold and waits for the merchant to confirm the payment by hand. When a vendor crypto widget failed for guests, we traced the cause: its early REST calls depended only on WooCommerce session state, which guest checkouts do not reliably keep. We fixed it with a must-use plugin that restores the order context once the order key is validated and passes the order ID into every widget request.
Technical Stack
The technologies and tools we used to build this solution
Frontend
Backend
APIs & Libraries
Infrastructure
Key Features Implemented
Core functionality and technical implementations
Tokenised Card Vault Connector
Card details are typed into a hosted iframe. The checkout accepts the token only from the vault's own origin, and the order stores just the token plus brand, last four digits and expiry.
Off-Site Laravel Payment Service
Orders are handed to a separate Laravel service that shows the card form and charges through Stripe. A gateway contract and factory keep the payment provider swappable.
HMAC-Signed Callbacks
Both directions are signed with HMAC-SHA256 over the exact raw JSON body and checked with a constant-time comparison. An unsigned or tampered callback cannot complete an order.
Account Rotation With Daily Limits
A direct-pay gateway picks a random active receiving account that still has daily capacity, records it on the order, and counts usage per account per day.
Crypto Checkout With Manual Confirmation
An offline crypto gateway places orders on hold awaiting payment and shows payment instructions on the thank-you page and in the customer email.
Classic and Checkout Blocks Support
The direct-pay and crypto gateways register a Checkout Blocks payment method alongside the classic gateway. The direct-pay gateway also assigns accounts to orders placed through the Store API.
Results & Impact
What this project achieved
- Card numbers never reach the WordPress database; orders hold only a vault token and masked card details
- Orders are marked paid only after a signed callback from the payment service passes HMAC verification
- Direct-pay orders are spread across receiving accounts automatically, and no account is assigned past its daily limit
- Crypto orders stay on hold until the merchant confirms the payment, so no crypto order is treated as paid before the funds arrive
- The direct-pay and crypto gateways are offered in both the classic checkout and WooCommerce Checkout Blocks
- The guest-checkout failure in a vendor crypto widget was traced to its root cause and patched with a must-use plugin, without editing the vendor plugin's files
Related Case Studies
Explore more of our technical work