sdpayhub / laravel-payzy
Enterprise-grade Laravel payment package with a unified API for Razorpay, Stripe, PayPal, Paytm, and PhonePe.
Requires
- php: ^8.1
- illuminate/cache: ^10.0|^11.0|^12.0
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/events: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/log: ^10.0|^11.0|^12.0
- illuminate/queue: ^10.0|^11.0|^12.0
- illuminate/routing: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.42
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.13
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
- phpstan/phpstan: ^1.10|^2.0
- phpstan/phpstan-deprecation-rules: ^1.1|^2.0
This package is not auto-updated.
Last update: 2026-07-27 14:47:26 UTC
README
One simple API for Laravel payments. Use Razorpay, Stripe, PayPal, Paytm, or PhonePe without learning five different SDKs.
composer require sdpayhub/laravel-payzy
use Sdpayhub\Payzy\Facades\Payzy; $response = Payzy::gateway('razorpay') ->amount(1000) // amount in paise (₹10.00) ->currency('INR') ->orderId('ORDER-1001') ->create();
Why Payzy?
| What you get | Why it matters |
|---|---|
| Same response shape on every gateway | Switch from Razorpay to Stripe by changing one string |
| Built-in webhook verification | Protects you from fake payment callbacks |
| Replay protection | Same webhook cannot be reused to fake a payment |
| Idempotency keys | Retries do not create duplicate charges |
| Secrets never logged | API keys are masked automatically |
| Typed exceptions | Catch payment errors clearly, not generic \Exception |
Requirements
- PHP 8.1 or newer
- Laravel 10, 11, or 12
Install (3 steps)
1. Install the package
composer require sdpayhub/laravel-payzy
2. Publish the config
php artisan vendor:publish --tag=payzy-config
3. Add your keys to .env
PAYZY_DEFAULT_GATEWAY=razorpay PAYZY_MODE=sandbox PAYZY_CURRENCY=INR RAZORPAY_KEY=your_key RAZORPAY_SECRET=your_secret RAZORPAY_WEBHOOK_SECRET=your_webhook_secret
Use sandbox keys while testing. Never commit .env.
First payment (copy & paste)
use Sdpayhub\Payzy\Facades\Payzy; $response = Payzy::gateway('razorpay') ->amount(1000) ->currency('INR') ->orderId('ORDER-1001') ->create(); if ($response->isSuccess()) { // Save this ID in your database $transactionId = $response->getGatewayTransactionId(); } if ($response->isRedirect()) { // Send the customer to the payment page return redirect()->away($response->getRedirectUrl()); } // Something went wrong return back()->with('error', $response->getMessage());
Important: amount format
Pass amounts in smallest currency units:
| Currency | Example | Meaning |
|---|---|---|
| INR | 1000 |
₹10.00 |
| USD | 1000 |
$10.00 |
Switch gateway
Only change the gateway name. The rest of your code stays the same.
Payzy::gateway('stripe')->amount(1000)->currency('USD')->orderId('ORDER-1')->create(); Payzy::gateway('paypal')->amount(1000)->currency('USD')->orderId('ORDER-1')->create(); Payzy::gateway('paytm')->amount(1000)->currency('INR')->orderId('ORDER-1')->create(); Payzy::gateway('phonepe')->amount(1000)->currency('INR')->orderId('ORDER-1')->create();
Common operations
$pay = Payzy::using('razorpay'); // Check status $status = $pay->status('pay_xxxxx'); // Full refund $refund = $pay->refund('pay_xxxxx'); // Partial refund (amount in paise/cents) $partial = $pay->partialRefund([ 'payment_id' => 'pay_xxxxx', 'amount' => 250, ]); // Capture (when payment was authorized first) $capture = $pay->capture([ 'payment_id' => 'pay_xxxxx', 'amount' => 1000, ]);
Every method returns the same PaymentResponse object:
$response->isSuccess(); // true / false $response->getGatewayTransactionId(); // gateway payment / order id $response->getMessage(); // human-readable message $response->getData(); // useful fields as array $response->isRedirect(); // needs browser redirect? $response->getRedirectUrl(); // URL to send the user to
Webhooks (important)
Payzy registers this route for you:
POST https://your-app.com/payzy/webhooks/{gateway}
Examples:
https://your-app.com/payzy/webhooks/razorpayhttps://your-app.com/payzy/webhooks/stripe
Setup checklist
- Put the URL in your gateway dashboard (Razorpay / Stripe / etc.).
- Put the webhook secret in
.env(RAZORPAY_WEBHOOK_SECRET,STRIPE_WEBHOOK_SECRET, …). - Run a queue worker so webhooks stay fast:
php artisan queue:work
Payzy will:
- Verify the signature
- Reject old or repeated events (replay protection)
- Queue the work and return
202 Accepted
Avoid duplicate charges
Always send an idempotency key for checkout / create / refund calls:
Payzy::gateway('razorpay') ->amount(1000) ->currency('INR') ->orderId('ORDER-1001') ->idempotencyKey('checkout-ORDER-1001') ->create();
If the same key is reused with a different payload, Payzy throws IdempotencyConflictException.
Cache note
Payzy stores idempotency results in Laravel’s cache. On a fresh Laravel app, prefer:
CACHE_STORE=file
If you use CACHE_STORE=database, run migrations first so the cache table exists. Otherwise create/refund with an explicit idempotency key will fail with a clear configuration error.
Listen for events
use Sdpayhub\Payzy\Events\PaymentSuccess; // In a listener: public function handle(PaymentSuccess $event): void { $gateway = $event->gateway; $txnId = $event->response->getGatewayTransactionId(); // Mark the order as paid in your app }
Useful events:
PaymentCreatedPaymentSuccessPaymentFailedRefundCreatedRefundCompletedWebhookReceivedWebhookFailed
Handle errors
use Sdpayhub\Payzy\Exceptions\PayzyException; use Sdpayhub\Payzy\Facades\Payzy; try { $response = Payzy::using('razorpay')->charge([ 'amount' => 1000, 'currency' => 'INR', 'order_id' => 'ORDER-1001', ]); } catch (PayzyException $e) { // Safe to log — secrets are not in the message by design report($e); return back()->with('error', 'Payment could not be started.'); }
Supported gateways
| Gateway | Payments | Refunds | Webhooks | Customers | Subscriptions |
|---|---|---|---|---|---|
| Razorpay | Yes | Yes | Yes | Yes | Yes |
| Stripe | Yes | Yes | Yes | Yes | Yes |
| PayPal | Yes | Yes | Yes | — | Yes |
| Paytm | Yes | Yes | Yes | — | — |
| PhonePe | Yes | Yes | Yes | — | — |
Extra .env keys
# Stripe STRIPE_SECRET= STRIPE_WEBHOOK_SECRET= # PayPal PAYPAL_CLIENT_ID= PAYPAL_CLIENT_SECRET= PAYPAL_WEBHOOK_ID= # Paytm PAYTM_MERCHANT_ID= PAYTM_MERCHANT_KEY= # PhonePe PHONEPE_CLIENT_ID= PHONEPE_CLIENT_SECRET= PHONEPE_MERCHANT_ID= PHONEPE_SALT_KEY=
Security basics (please read)
- Keep all secrets in
.env— never hardcode keys in PHP files. - Use
PAYZY_MODE=sandboxuntil you are ready for live traffic. - Always configure webhook secrets and keep verification enabled.
- Serve webhook URLs over HTTPS only.
- Run
php artisan queue:workin production so webhook handlers are queued. - Do not disable SSL verification (Payzy never does this for you).
Report security issues privately — see SECURITY.md.
Testing this package
composer test
composer analyse
composer lint
Packagist auto-update (maintainers)
If Packagist shows “This package is not auto-updated”, GitHub is not notifying Packagist on push. Fix it once:
- Open Packagist → your profile → Show API Token.
- On GitHub: Settings → Webhooks → Add webhook
- Payload URL:
https://packagist.org/api/github?username=YOUR_PACKAGIST_USERNAME - Content type:
application/json - Secret: your Packagist API token
- Events: Just the push event
- Payload URL:
- Back on the package page, click Update so tags and the README sync immediately.
After that, every push and tag appears on Packagist automatically (including the README below the install command).
Need more detail?
- Upgrade notes: UPGRADING.md
- How to contribute: CONTRIBUTING.md
- Changelog: CHANGELOG.md
License
MIT — see LICENSE.