30 July 2024 / Digital products

Paid modules change more than the checkout screen

Adding paid access to a learning platform touches enrolment, permissions, account state, completion records, certificates and support. I am breaking the work into those consequences because a clean payment screen does not help if the learner receives the wrong access or the records disagree afterwards.

Intersecting steel beams and bracing viewed from beneath a bridge.
Photo: Sebastian Schuster (opens in a new tab)

A paid module joins two systems that often have very different ideas about state. The payment service knows about an attempt, an authorisation, a capture and perhaps a refund. The learning platform knows about a person, an enrolment, access to material and progress through it. The product has to turn one set of events into the other without treating a successful browser redirect as proof that everything worked. Before polishing checkout, define the purchase, entitlement, enrolment and access transitions that have to follow it. "Access to a course" sounds precise until implementation starts. Access may begin immediately or on a scheduled date. It may last forever, expire after a period, cover one module, include a bundle or depend on a subscription that can later be paused. Write the entitlement down as data. At minimum, it needs a subject, the learning item or bundle, a source, a start, an optional end and a current status. The source might be a payment, a manual grant, an organisational licence or a promotion. Keeping the source matters when support needs to explain why access exists or remove one grant without disturbing another.

The purchase record and entitlement should have separate identifiers. One purchase may create several entitlements, and one learner may hold overlapping grants. A single has_paid flag on the account cannot represent those cases safely. Decide the boundary around identity too. If somebody pays before signing in, the system needs a controlled way to attach the purchase to an account. An email address alone is a poor permanent key because addresses change, aliases exist and typing errors happen. The learner's return to a success page is useful feedback, but it is not a reliable fulfilment signal. They can close the tab, lose the connection or revisit the URL. The browser may also arrive before the payment provider's server notification. Fulfilment should consume a verified event from the payment provider. Verify its signature, retain the provider's event identifier and process it idempotently. If the same event arrives twice, the second attempt should find the completed work and stop without issuing another enrolment or email. A practical sequence is:

  1. Store the incoming event and its verification result.
  2. Match it to an internal purchase using a stable reference created before checkout.
  3. Update the purchase state only if the transition is allowed.
  4. Create or update the entitlement.
  5. Record each follow-on action, including enrolment and notification.

The event handler does not need to finish every task in one request. It does need to leave enough durable state for a worker to continue and for a person to see where processing stopped. An account answers who can sign in. An enrolment connects that account to a learning item. A permission answers what the person can open now. Those records often move together, but they are not the same thing. A paid learner might have a valid account and enrolment while access is waiting for a start date. A refunded purchase might leave completion history intact while removing permission to open the paid material. A staff member may need support access without appearing as a learner. Collapsing these conditions into one status creates special cases in every screen. Permission checks should use the current entitlement rather than infer access from a historical payment. Keep the rule in one place and apply it to page requests, downloads, APIs and background jobs. Hiding a link in the interface is not an access control. Also decide what happens when two sources grant the same module. Removing an expired subscription should not revoke access that still exists through a separate purchase or licence. Completion records and certificates are evidence of learning activity. A cancellation, failed renewal or refund changes the commercial relationship, but it should not silently rewrite what the learner completed.

Model progress independently from current access. If access ends, the platform can prevent further study while retaining completed lessons, assessment attempts and issued certificate references according to the product's policy. If a refund must invalidate a certificate, make that an explicit rule and record the reason. Do not derive it accidentally from a missing entitlement. Certificate generation also needs idempotency. A retry after a timeout should find the existing certificate or safely resume generation. Otherwise a temporary failure can produce duplicate certificate numbers, duplicate emails or a completion screen that disagrees with the learner's record. Any rule that changes historical learning data deserves a written policy before it becomes code. Support staff will eventually be asked to explain it. The first implementation path usually follows a successful payment. Most support work appears elsewhere: the provider accepted payment but enrolment failed, the learner used a different email address, a refund arrived after completion, or a notification could not be delivered. Give each step a visible status and error record. "Payment complete" should be distinguishable from "entitlement created" and "enrolment active". A retry should target the failed step rather than replay the whole purchase blindly. Refund handling needs its own transition rules. Confirm whether the refund is full or partial, which entitlement it affects, whether access ends immediately and what evidence must remain. Charge disputes may need a different state from an ordinary refund because they can change again.

Manual correction should use the same domain operations as automated processing. If support can edit database fields directly but the application uses validated transitions, the two paths will eventually disagree. A useful support screen follows one purchase across systems. It should show the learner account, internal purchase reference, provider reference, payment events, entitlements, enrolments, notifications and any failed work. Times should include a clear timezone. The screen should also distinguish facts from actions. Staff need to see what happened before they decide whether to retry enrolment, resend a message, grant temporary access or escalate a payment question. Destructive or financial actions should require an explicit confirmation and leave an activity record. Before release, run a matrix that covers a clean purchase, duplicate notification, delayed notification, failed enrolment, account mismatch, refund and retry. For each case, write down and then verify the expected payment state, entitlement, enrolment, progress visibility and support view. Any case with an unclear expected state needs a product rule before release.

Continue the thinking.

Comments are public and hosted in an open-source GitHub Discussions repository.

Loading comments connects your browser to GitHub. A GitHub account is required to post.

All blogs