khumam / laravel-midtrans
Laravel Midtrans payment gateway integration package
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/routing: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
README
Laravel package for Midtrans payment gateway integration. Supports Laravel 11, 12, and 13.
Inspired by Laravel Cashier (Paddle). Simplified flow: checkout and webhook handling.
Installation
composer require khumam/laravel-midtrans
Publish config and migrations:
php artisan vendor:publish --tag=midtrans-config php artisan vendor:publish --tag=midtrans-migrations php artisan migrate
Add to .env:
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxx MIDTRANS_IS_SANDBOX=true
Setup
Add the Billable trait to your User model (or any billable model):
use Khumam\Midtrans\Billable; class User extends Model { use Billable; }
Usage
One-Time Payment
use Khumam\Midtrans\Billable; // In your controller public function pay(Request $request) { return $request->user() ->checkout(50000) ->withItemDetail([ ['id' => 'item1', 'price' => 50000, 'quantity' => 1, 'name' => 'Product A'], ]) ->secureCreditCard() ->redirectTo('payment.success'); }
The checkout() method:
- Cancels any existing pending transactions for the user
- Creates a new transaction record
- Calls the Midtrans Snap API
- Redirects user to the Midtrans payment page
Subscription
use Khumam\Midtrans\Enums\MidtransPeriod; // Monthly subscription return $user->subscribe(100000, MidtransPeriod::Monthly) ->withSubscriptionSchedule([ 'interval' => 1, 'interval_unit' => 'month', ]) ->redirectTo('subscription.success'); // Annual subscription return $user->subscribe(1000000, MidtransPeriod::Annually) ->redirectTo('subscription.success');
Available periods:
| Period | Enum Value |
|---|---|
| Monthly | MidtransPeriod::Monthly |
| Quarterly | MidtransPeriod::Quarterly |
| Semi-Annually | MidtransPeriod::SemiAnnually |
| Annually | MidtransPeriod::Annually |
Check Subscription Status
// Check if user has active subscription if ($user->subscribed()) { // User is subscribed } // Get the active subscription transaction $subscription = $user->subscription(); // Returns Transaction model or null
Checkout Builder Methods
All methods below are chainable on the Checkout object returned by checkout() or subscribe().
Payment Options
->secureCreditCard() // Enable 3DS/secure credit card ->withCreditCard([ // Custom credit card config 'token_id' => 'xxx', 'bank' => 'bni', 'installment_term' => 3, ]) ->withBankTransfer([ // Bank transfer (VA) 'bank' => 'bca', 'va_number' => '1234567890', ]) ->withGopay([ // GoPay config 'enable_callback' => true, 'callback_url' => 'https://example.com/callback', ]) ->withQris([ // QRIS config 'acquirer' => 'gopay', ])
Transaction Details
->withItemDetail([ // Item details ['id' => 'item1', 'price' => 50000, 'quantity' => 1, 'name' => 'Product A'], ['id' => 'item2', 'price' => 25000, 'quantity' => 2, 'name' => 'Product B'], ]) ->withCustomerDetail([ // Override customer details 'first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john@example.com', 'phone' => '08123456789', 'billing_address' => [ 'first_name' => 'John', 'address' => 'Jl. Sudirman No. 1', 'city' => 'Jakarta', 'postal_code' => '12190', 'country_code' => 'IDN', ], ]) ->withCustomExpiry([ // Custom expiration 'expiry_duration' => '60', 'unit' => 'minute', ])
Default customer details are auto-populated from the billable model's name and email fields. Override with custom field names:
$user->checkout(50000, [ 'name_field' => 'full_name', 'email_field' => 'email_address', ]);
Subscription Schedule
->withSubscriptionSchedule([ 'interval' => 1, 'interval_unit' => 'month', 'max_interval' => 12, ])
Finalize
->redirectTo('route.name') // Redirects user to Midtrans Snap payment page
Webhook
The package automatically registers a webhook endpoint at POST /midtrans/webhook.
Configure this URL in your Midtrans Dashboard under Settings > Payment Notification URL:
https://yourdomain.com/midtrans/webhook
Webhook Flow
- Receives POST from Midtrans
- Validates signature:
SHA512(order_id + status_code + gross_amount + server_key) - Updates transaction status
- Stores full response in
midtrans_transaction_responsestable
Transaction Statuses
| Status | Meaning |
|---|---|
capture |
Card payment captured successfully. Funds received. |
settlement |
Transaction settled. Funds credited to account. |
pending |
Awaiting customer payment. |
deny |
Payment rejected by provider or fraud system. |
cancel |
Transaction cancelled. |
expire |
Payment window expired. |
failure |
Unexpected error during processing. |
refund |
Full refund issued. |
partial_refund |
Partial refund issued. |
authorize |
Card pre-authorized (advanced feature). |
Transaction Model
use Khumam\Midtrans\Models\Transaction; $transaction = Transaction::where('order_id', $orderId)->first(); $transaction->isPaid(); // true if capture or settlement $transaction->isPending(); // true if pending $transaction->isFailed(); // true if deny/cancel/expire/failure $transaction->isRefunded(); // true if refund or partial_refund $transaction->onGracePeriod(3); // true if ends_at passed < 3 days ago $transaction->billable; // The User model (morph relationship) $transaction->responses; // All webhook responses (HasMany) $transaction->latestResponse; // Latest webhook response (HasOne)
License
MIT