Architecture fit
omnipay/payfast is a driver for the Omnipay gateway abstraction layer, not a Laravel package. Fit is therefore conditional: it slots in cleanly if we adopt league/omnipay (or omnipay/common v3) as the payment port, and it maps naturally onto a ports-and-adapters design — define our own PaymentGateway contract, implement PayFastGateway that delegates to Omnipay\Omnipay::create('PayFast'), and keep Omnipay\* types out of domain code, controllers and Eloquent models.returnUrl/cancelUrl, and out-of-band ITN (Instant Transaction Notification) webhook for authoritative settlement. This is an offsite/redirect integration, so PCI scope stays at SAQ-A and no card data touches our servers — a genuine architectural win versus an on-site form build.Integration feasibility
omnipay/payfast + omnipay/common (v3) and resolve a gateway from config. The real work is not the driver — it is the surrounding state machine: order → payment attempt → redirect → ITN receipt → signature validation → server-to-server confirmation → amount/merchant reconciliation → idempotent ledger write → order state transition. Budget for that, not for the install.config/services.php from env, a singleton gateway factory, two named routes (browser return + ITN webhook), the ITN route excluded from CSRF (it is application/x-www-form-urlencoded, not JSON), and the whole webhook handler pushed onto a queue after the synchronous validation step.Technical risk
thephpleague/omnipay-payfast (the canonical Omnipay driver home), verify the Packagist record, and diff the tagged release against the repo HEAD. An unverifiable repo is a supply-chain question, not a cosmetic one.hash_equals, canonical parameter ordering and URL-encoding reproduced exactly (encoding/ordering/casing mismatches are the single most common breakage), and never trusting browser-returned parameters as evidence of payment.omnipay/common ^3 pinning, Guzzle 7 / php-http/discovery conflicts, and deprecation noise. The driver uses HTTPlug, so we will run a second HTTP stack alongside Laravel's Http client — two clients to instrument, retry and observe.barryvdh/laravel-omnipay and similar bridges are themselves stale; plan to resolve the gateway through Omnipay's factory in our own service provider rather than adding another abandoned dependency.Key questions
acceptNotification() or only completePurchase()?token parameter, subscriptions), and if so, is card-token storage handled or only referenced?omnipay/common version matrix, and does it install cleanly on our Laravel 10/11 + PHP 8.2/8.3 baseline without conflicts?Stack fit
PaymentGateway interface with PayFastGateway as the only class that imports Omnipay\*. This is the single most valuable design decision here — it means swapping to PayFast's native on-site API, or adding Ozow/Peach/Yoco for the ZA market, costs one adapter, not a checkout rewrite.Migration path
Compatibility
composer.json (omnipay/payfast, omnipay/common, HTTP client) and commit the lockfile. Do not allow float on a dormant package.php-http/discovery behaviour when multiple HTTP clients are installed (a classic source of "works locally, fails in prod" ambiguity).--ignore-platform-reqs. Ignoring platform requirements on a payments dependency is not acceptable.Sequencing
PaymentGateway port, implement PayFastGateway, wire config + service provider, feature flag off.pf_payment_id, queued processing, dead-letter handling, replay-safe state transitions.Maintenance
Support
Scaling
pf_payment_id plus a transaction-state transition guard means duplicate ITN deliveries (which will happen) are harmless. Without it, retries turn into double-fulfilment.Failure modes
+), case sensitivity in hash comparison. Presents as "every payment rejected"; a canary transaction on deploy is the cheapest detector.Ramp-up
How can I help you explore Laravel packages today?