Accepting Midtrans Payments in Laravel: What Is Worth Getting Right
I have built several products on Midtrans with Laravel: an e-book store, a learning platform, an event-ticketing site, an online bakery and a multi-role beauty store. The checkout page always looks like the hard part. It is not. The parts that decide whether the money and the orders agree with each other are quieter.
The browser is not the source of truth
After paying, the customer is sent back to your site. It is tempting to mark the order as paid right there. Do not. A customer can close the tab, lose signal or hit back before the redirect happens, and a redirect can be forged by anyone. The payment status should come from Midtrans telling your server directly, through the notification (webhook) endpoint.
Verify the notification
Anyone can send a request to your notification URL. Midtrans includes a signature_key that is a SHA-512 hash of the order id, status code, gross amount and your server key. Recompute it and compare before trusting anything in the request. If it does not match, ignore it. As an extra check, you can ask Midtrans for the transaction status with the order id and compare.
Expect the same notification more than once
Notifications can arrive twice, late or out of order. Your handler should be idempotent: running it again must not ship a second parcel, add stock twice or send two emails. A simple rule that works is to only move an order forward (pending to paid, never paid back to pending) and to record that the side effects already ran.
Make order ids and amounts boring
Use an order id that is unique per attempt and tied to your own order record. Keep the amount in whole rupiah and calculate it on the server, never from a hidden form field. If a customer retries payment on the same order, decide in advance whether that creates a new Midtrans order id.
Test the unhappy paths
Sandbox makes the happy path easy. Spend the time on the others: expired payments, cancelled payments, a closed tab, a notification that arrives before the customer is redirected, and a webhook that is down for a minute. Those are the cases a real shop meets in its first week.
Keep keys and environments apart
Server keys belong in the environment file, never in the repository or the front end. Keep sandbox and production keys clearly separated and double check which one a deployment is using before launch day.
None of this is exotic. It is a short checklist, and it is the difference between a checkout that looks finished and one that quietly stays correct.