ABhome Admission — Paid Event Registration with Midtrans

Added a paid-event module to ABhome’s admission app: admin event management, Midtrans checkout with webhook-driven payment status, promo codes, ticket lookup verified by WhatsApp number, and Excel exports of registrants and responses. I moved the payment logic into its own service layer so the event controllers stay thin.
ABhome Admission is the admission system for ABhome Education. It handles school registration, student billing, and paid events, and it serves 550+ users. Between August and November 2025 I built the paid-event module inside a Laravel 8 codebase shared with several other developers.
The problem
The app already took Midtrans payments for school registration. Events needed a flow of their own: registration forms that change per event, ticket prices and promos, payment status that updates without anyone checking Midtrans by hand, and a way for registrants to find their ticket again.
What I built
- Admin event management, with event lists, participant data, and a summary per event.
- Registration forms with per-event fields, stored as JSON responses.
- Midtrans Snap checkout, with a webhook that verifies the SHA-512 signature and moves each ticket through pending, paid, denied, cancelled, or expired.
- Promo codes with date windows, a minimum participant count, usage caps, and partial discounts when a promo covers only some of the seats. A promo use only counts once the payment settles.
- Ticket lookup that asks for the ticket ID and the WhatsApp number used at registration, and returns a generic “not found” on a mismatch.
- Ticket cancellation, and Excel exports of registrants and form responses.
- Payment and promo logic in
EventServiceandMidtransService, so the event controllers validate input and delegate.
A decision worth explaining
The Midtrans PHP SDK requests Snap tokens over cURL, and that call failed on the production host. Rather than tie checkout to the host’s cURL setup, MidtransService tries the SDK first, logs a warning when it fails, and then sends the same request directly through Guzzle. Checkout keeps working on hosting I don’t control, and the SDK path takes over again if the host changes.
Result
The module is live in production. 35+ school registrations and 15+ event registrations have gone through ABhome Admission, and the event ones use the checkout described here.
- Stack
- Laravel 8 · MySQL · Midtrans · Laravel Excel
- Skills
- Payment integration · Webhook handling · Service-layer refactoring