kreetancraft / laravel-payment-gateway
Unified payment gateway for Laravel with Stripe and Himalayan Bank (2C2P PACO) support. Features encrypted database-stored credentials, provider manager, coupons, Livewire 4 + Flux UI.
Package info
github.com/Kreetancraft/laravel-payment-gateway
pkg:composer/kreetancraft/laravel-payment-gateway
Requires
- php: ^8.2
- ext-gmp: *
- ext-openssl: *
- guzzlehttp/guzzle: ^7.0|^8.0
- laravel/framework: ^11.0|^12.0|^13.0
- livewire/flux: ^1.0|^2.0
- livewire/livewire: ^3.0|^4.0
- lorisleiva/laravel-actions: ^2.7|^3.0
- nesbot/carbon: ^3.0
- sandermuller/laravel-fluent-validation: ^1.0
- spatie/laravel-data: ^3.0|^4.0
- spatie/laravel-permission: ^5.0|^6.0|^7.0|^8.0
- spatie/laravel-query-builder: ^5.0|^6.0|^7.0
- stripe/stripe-php: ^13.0|^14.0|^15.0|^16.0|^17.0|^18.0|^19.0|^20.0|^21.0
- web-token/jwt-framework: ^4.0
Requires (Dev)
- fakerphp/faker: ^1.24
- laravel/pint: ^1.20
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
- kreetancraft/laravel-user-management: Discovers this package's policies and creates their permissions automatically.
Provides
None
Conflicts
None
Replaces
None
README
Unified payments for Laravel — Stripe and Himalayan Bank (2C2P PACO), encrypted database credential management, a provider manager UI, full coupon system with smart stacking, refunds, and automated webhooks. Livewire 4 + Flux UI.
Ships no layout, no hardcoded styling, no user model and no external dependencies — it renders into your application and works with whatever you already have.
Design decisions worth knowing before you install
Credentials and configurations are encrypted and stored in the database. You do not need to edit
config files or restart workers to rotate keys or enable a gateway. All sensitive credentials
(Stripe secrets, HBL private keys, API tokens) are encrypted with a dedicated .env secret key
(PAYMENT_GATEWAY_ENCRYPTION_KEY). Even if your database is dumped or compromised, your gateway
credentials remain protected.
It does not care which user model you have. Payments and coupon usages resolve their customer
through config('auth.providers.users.model'); every policy type-hints Authenticatable.
It names no permission of its own. Its screens ask standard authorization questions and its policies answer them. Until permissions exist anywhere in the app they are open, so it works on a bare install rather than failing closed.
It is architected around SOLID principles and easily extensible. Every payment driver implements
PaymentGateway contract (extending AbstractGateway). To add PayPal, Esewa, Khalti, or any other
provider, simply create a new class implementing the interface and register it in the database.
Flux is a hard dependency. The admin management views use <flux:*> throughout.
Installation
composer require kreetancraft/laravel-payment-gateway
php artisan migrate
php artisan db:seed --class="Kreetancraft\PaymentGateway\Database\Seeders\GatewaySeeder"
Let Tailwind see this package
Required. Tailwind v4 generates only the classes it finds by scanning files, and it does not scan
vendor/. In resources/css/app.css:
@source '../../vendor/kreetancraft/laravel-payment-gateway/resources/views';
Skipping it fails confusingly rather than loudly — classes shared with your own views still work and only the ones unique to this package go missing.
Database Encryption & Security
To protect gateway secrets if your database is ever compromised, set a dedicated encryption key in
your .env:
PAYMENT_GATEWAY_ENCRYPTION_KEY=base64:YOUR_32_BYTE_BASE64_KEY_HERE
(If not set, the package seamlessly falls back to your standard APP_KEY)
All API keys, secrets, and private RSA keys entered in the admin interface are encrypted on the fly
before writing to payment_gateways.credentials and decrypted only in memory when preparing an API payload.
No files, paths, or storage/ directory permissions are required — everything lives securely in your database.
Gateway Capabilities
| Gateway | Integration | Flow | Currencies | Capabilities |
|---|---|---|---|---|
| Stripe | Stripe SDK / PaymentIntent | Embedded / Elements | USD, EUR, GBP, AUD, CAD, NPR, INR | Charge, Refund, Webhook, Verify |
| Himalayan Bank | 2C2P PACO (JOSE/JWE) | Hosted 3DS Redirect | NPR, USD, THB | Charge, Void/Refund, Webhook, Verify |
Adding Custom Payment Gateways
Any new gateway can be added without altering the package core (Open/Closed principle):
- Create a class extending
AbstractGateway:
namespace App\Gateways; use Kreetancraft\PaymentGateway\Gateways\AbstractGateway; use Kreetancraft\PaymentGateway\Data\PaymentResult; use Kreetancraft\PaymentGateway\Data\RefundResult; use Kreetancraft\PaymentGateway\Data\VerificationResult; use Kreetancraft\PaymentGateway\Data\WebhookResult; class MyCustomGateway extends AbstractGateway { public function charge(array $data): PaymentResult { ... } public function refund(string $transactionId, float $amount): RefundResult { ... } public function verify(array $data): VerificationResult { ... } public function webhook(array $payload): WebhookResult { ... } public function getCode(): string { return 'custom'; } public function getLabel(): string { return 'Custom Gateway'; } public function checkoutRedirect(): bool { return true; } }
- Register the gateway in the database via the admin UI (
/payment/gateways) or a migration.
The Provider Manager UI
The package provides full Livewire 4 + Flux UI management screens for configuring gateways and coupons:
<!-- Mount directly in any Blade view or route --> <livewire:payment.gateways /> <livewire:payment.coupons /> <livewire:payment.checkout />
Available admin routes (customizable in config/payment-gateway.php):
GET /payment/gateways— Manage gateway credentials, toggle status, configure environmentsGET /payment/manage-coupons— Create, edit, and track coupon usagesGET /payment/checkout— Hosted customer checkout with coupon input and gateway selection
Coupons & Smart Stacking
A comprehensive discount engine with multiple coupon types and intelligent stacking algorithms:
| Type | Calculation | Behavior |
|---|---|---|
percentage |
amount * value / 100 |
Percentage discount with optional maximum cap |
fixed |
min(value, amount) |
Fixed monetary deduction in cents |
buy_x_get_y |
Item-level computation | Quantity-based discount |
tiered |
Tier-threshold rules | Incremental discount based on total order size |
free_shipping |
Shipping waiver | Non-monetary discount that always stacks on top |
Stacking Logic
- Monetary Coupons: Evaluates combinations to maximize customer savings without exceeding order total.
- Free Shipping: Always stacks on top of any monetary discount.
- Restrictions: Enforces minimum order amount, per-user limits, total limits, expiry dates, and user ID whitelists.
Permissions
Every policy declares a subject, so with kreetancraft/laravel-user-management installed one command creates all of them:
php artisan user-management:sync-permissions
| Subject | Abilities |
|---|---|
payment |
viewAny, view, create, update, delete, refund |
gateway |
viewAny, view, update, toggle |
coupon |
viewAny, view, create, update, delete |
Gateways, Coupons, and Transactions links automatically contribute themselves to the
admin sidebar through container tags (payment.navigation.items).
What can be paid for
A checkout names a thing, never a price. Implement Payable on whatever you
sell and register it under an alias:
// config/payment-gateway.php 'payables' => [ 'invoice' => \App\Models\Invoice::class, ],
class Invoice extends Model implements Payable { public function paymentAmountCents(): int { return $this->balance_due_cents; } public function paymentCurrency(): string { return $this->currency; } public function paymentReference(): string { return $this->number; } public function paymentDescription(): ?string { return "Invoice {$this->number}"; } }
POST /api/v1/payment/checkout { "payable_type": "invoice", "payable_id": 42 }
The amount and currency are read off the model. An amount_cents in the request
is ignored — earlier versions accepted it on a public route, which meant the
buyer chose the price. An alias not in the allowlist is refused, so a caller
cannot point checkout at an arbitrary model.
Return what is still outstanding, not the original total: that is what gets charged, and a partly-paid payable must not be charged twice over.
Public & Private API
POST /api/v1/payment/checkout Initiate a charge for a payable
GET /api/v1/payment/gateways Enabled gateways and their currencies
POST /api/v1/payment/coupons/validate Validate a code the caller already holds
POST /api/v1/payment/coupons/apply Calculate a discount
POST /api/v1/payment/webhook/{gateway} Gateway callback
Behind auth:
POST /api/v1/payment/verify Ask the gateway about a transaction
GET /api/v1/payment/coupons Every active code and its value
There is no refund endpoint. Refunding moves money out; it happens on the transactions screen, authorized against the payment. Everything above is rate limited — the webhook because each request costs an outbound call to the bank, the rest because they either probe for valid references or cost money.
Operations
php artisan payment-gateway:status
Names every missing credential and exits non-zero, so a deploy can gate on it. Worth running before go-live: every way a gateway is misconfigured looks the same from outside, and the buyer is the one who finds out.
php artisan payment-gateway:reconcile
Re-asks the gateway about payments still pending. Schedule it. A dropped callback otherwise means money taken and an order that never completes, with nothing that knows to look. Safe to run often — verification is idempotent and never downgrades a settled payment.
Requirements
PHP 8.2+ with ext-openssl and ext-gmp, Laravel 12 or 13, Livewire 4, Flux 2.
ext-gmp is not optional in practice — the vendor's own integration notes call for it to
avoid RSA timeouts on the JOSE handshake.
License
MIT.