Skip to main content
Complete work archive

Production / Learning management

Prasmul ELI Career Companion

A learning and career-companion platform for executive education programs.

Relationship
Freelance · Prasetiya Mulya University
Role
Full-stack Engineer
Period
May 2022 – Jan 2024

Summary

A direct freelance engagement with Prasetiya Mulya University, concurrent with my SoftwareSeni role, covering executive-course enrollment and payments. Most of my backend work sat on the path from purchase to access: Midtrans checkout with callback signature and status-API checks before any enrollment, invoice PDFs and confirmation email after a verified payment, and account provisioning into CoursePlayer, a separate Laravel application, so learners could open paid content without signing in again. I also built voucher enrollment on the normal order and enrollment records, and integrated the university's private SSO package while the public pages moved from Vue to server-rendered Laravel for search indexing. I walked each payment state through the Midtrans sandbox one at a time.

At a glance

  • Carried a course purchase across six boundaries: sign-in, payment, enrollment, invoicing, provisioning, content access
  • Granted access from a verified Midtrans callback, never from the browser returning to a success URL
  • Moved the public experience from Vue to Laravel-rendered pages to meet an SEO indexing requirement
  • Provisioned Career Companion accounts into CoursePlayer, a separate application with its own users
  • Backfilled existing accounts with a command that reported each rejection and carried on
  • Direct client engagement; other engineers worked on CoursePlayer and GoCampus

Stack: PHP, Laravel, Vue, Blade, Tailwind CSS, MySQL, Midtrans

Read the full case study2 stories · ~4 min read

Inputs

Institutional identity
Midtrans payments
Program content
Career activity
Learning management

Outputs

Authenticated sessions
Enrollments and invoices
CoursePlayer access
Career records
What this system takes in, and what reads from it.
Public homepage (V1, server-rendered for indexing).

System context

Prasmul ELI Career Companion supported executive courses, enrollment, payments, learner profiles, and career programs at Prasetiya Mulya University.

The product changed while I worked on it. V0 used Vue; after the SEO team found that its search visibility did not meet the product's needs, V1 moved the public experience to Laravel-rendered pages. The backend remained Laravel and MySQL. Separate applications handled CoursePlayer content and GoCampus career classes, talks, and assessment entry points.

I worked directly with the university from May 2022 to January 2024, alongside my SoftwareSeni employment. Backend was the center of my work: application-side SSO integration, Midtrans (an Indonesian payment gateway) payments, enrollment, CoursePlayer account provisioning and access handoff, program administration, and the interfaces around those workflows.

My scope

I owned

  • The application-side integration with the institution's private SSO package.
  • Midtrans checkout, callback verification, transaction-status checks, enrollment, and invoice flows.
  • Career Companion account provisioning into CoursePlayer and the authenticated content handoff.
  • Voucher-based enrollment and permission extensions across business modules.
  • Registration-source and career-interest profile fields.

I contributed to

  • Frontend work across the Vue V0 and Laravel-rendered V1 applications.
  • GoCampus program pages and the personalized career-assessment entry flow.
  • Responsive Blade, Sass, Tailwind CSS, and jQuery work.
  • GitLab CI and release support.

Team-owned

This was a direct client engagement, outside SoftwareSeni. The applications and their foundations predated parts of my work; the university owned program requirements and priorities, and other engineers contributed to CoursePlayer and GoCampus. I took my assigned work through implementation and delivery.

Key engineering work

Turning a verified payment into course access

Payments

Problem

A Midtrans checkout began as a pending local order. Provider callbacks then had to be matched to that order and checked before payment state could grant access to direct courses or every course inside a purchased bundle.

Action

I implemented Snap-token creation and configurable sandbox and production endpoints. The callback layer handled normal responses, webhooks, and provider-specific return formats; reconstructed the SHA-512 signature; queried Midtrans's status API; and distinguished capture or settlement from pending, denied, canceled, expired, and failed transactions.

For a verified successful transaction, the backend completed the existing order, recorded earnings, enrolled the learner, generated the invoice PDF, and queued the confirmation email.

Result

Course access and invoicing followed confirmed Midtrans state instead of a browser return alone. Successful direct-course and bundle payments created the corresponding enrollments, while pending and failed states remained separate. I walked each transaction state through the Midtrans sandbox, one at a time, and watched what the backend did with each.

Pending student orders awaiting verified payment before enrollment.

CoursePlayer was a separate application with its own users

Cross-application workflow

Problem

CoursePlayer was a separate Laravel application with its own users and access tokens. New Career Companion accounts had to be provisioned there, existing accounts needed a backfill path, and a learner opening paid content should not have to complete a second interactive login.

Action

I implemented both sides of the account-provisioning contract: Career Companion sent profile data through signed registration requests, and CoursePlayer accepted the fields and returned its local user representation and token. New registrations triggered provisioning without making a CoursePlayer failure block the local account, while a console command could process existing users and report failures per account. The HTTP client retried timeouts up to three times.

Before handing off content, Career Companion checked ownership of the direct course or its bundle, requested a CoursePlayer access token, cached it per user for 60 minutes, and redirected to the original CoursePlayer content URL.

Result

New and existing Career Companion users had explicit provisioning paths into CoursePlayer. Learners with course access could open the corresponding content without a second interactive sign-in, and one failed account did not stop the bulk backfill from continuing. I ran the migration command over real Career Companion users. It carried on past the accounts CoursePlayer rejected as already existing and reported each failure on its own.

Other contributions

  • Sign-in across two generations

    Integrated the institution's private SSO package at the application boundary, so the existing Google sign-in led into the Career Companion and Career Development Center context through the move from Vue (V0) to server-rendered Laravel pages (V1). The SEO team confirmed indexing against the sitemap after the move.

  • Voucher enrollment

    Vouchers with active, expiry, and usage state, bulk code creation, and permission-aware administration. A redeemed voucher creates a completed order and attaches the learner to the course, so voucher access is an ordinary enrollment with an order record.

  • GoCampus career programs

    Contributed to the GoCampus program experience combining Career Classes, Career Talks, Career Interest, a Career Development Plan, and Job Access. The Career Interest path resolved a per-student assessment URL, including links assigned through spreadsheet imports, and showed a clear unavailable state when no link had been assigned.

  • Permissions and administration

    Extended granular permissions across lessons, teachers, taxes, reports, payment approvals, coupons, sliders, testimonials, FAQs, subcategories, and vouchers, then applied them to controllers and permission-aware administration actions.

  • Delivery

    Worked across responsive server-rendered course and administration interfaces and adjusted GitLab CI for branch-specific deployment, cache rebuilding, migrations, and controlled seeding.

Technical decisions

V1 served the course and program pages from Laravel instead of the Vue application, because the pages had to be indexable and server rendering was the shorter path to that. The cost was two rendering models in one product, and the constraint that kept it workable was a single login path into the Career Companion and Career Development Center context, so a learner never signed in twice to cross between them.

Payment state came from the confirmed Midtrans callback, not from the browser returning to a success URL. The browser return is the easier signal to trust and the wrong one, since a learner can reach that URL without the transaction having settled. The same reasoning shaped the CoursePlayer backfill: the run reports each rejected account and carries on, because stopping the whole migration on one account already present in CoursePlayer would turn a routine collision into an outage of the provisioning path.