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
Website
https://prasmuleli-cc.id (opens in a new tab) (View archived website)

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

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.

What I built

Integrated the application with institutional SSO and built Midtrans, enrollment, CoursePlayer access, and program workflows across Vue V0 and Laravel-rendered V1.

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 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 rather than a SoftwareSeni assignment. 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

Keeping one sign-in path across two product generations

Identity and product migration

Problem

V0's Vue interface did not meet the SEO team's search-visibility requirements, so V1 moved the public experience to server-rendered Laravel pages. The application also had to connect an existing Google sign-in experience and a private SSO package used for the CC and CDC service context without introducing a separate account path.

Action

I contributed frontend work across both versions, improved the existing social sign-in experience, and made provider links available in shared login surfaces. On the backend, I integrated the private SSO package at the application boundary so the existing identity flow could enter the CC and CDC context.

Result

V1 could serve indexable Laravel-rendered pages while preserving a single application login path into the CC and CDC context. The SEO team checked indexing against the sitemap after the move and confirmed it. They raised the requirement in the first place, so the confirmation came from the people who would know.

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. Reading the callback code would have suggested the same separation between settled and unsettled states, but watching it happen is different evidence.

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, reporting each failure on its own, and I watched it do that. The resilience is something the run showed me, not something I inferred from the code.

Making voucher enrollment part of the existing course model

Enrollment and authorization

Problem

The university needed to grant course access through vouchers without bypassing the platform's order history, enrollment model, or administrative permissions.

Action

I implemented unique vouchers with active, expiry, and usage state; creator and redeemer ownership; bulk code creation; status controls; and permission-aware administration. Redemption checked that the user was a student, that the voucher was valid, and that the learner did not already own the course. A successful claim recorded the redeemer, created a completed voucher order and item, and attached the learner to the course.

Result

Voucher access became a normal course enrollment with an order record, not a separate entitlement path. Administrators could create and manage codes through the same role-based backend used for the rest of the platform.

Other contributions

  • 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.

Engineering takeaway

A learner buying a course crossed six boundaries: sign-in, payment verification, enrollment, invoicing, CoursePlayer provisioning, and content access. Each transition had to be checked on its own: a verified Midtrans status before enrollment, course ownership before the content handoff. The Vue and Laravel-rendered pages still had to present the whole thing as one purchase.