Design a Payment and Certificate System
Payments are one of the most sensitive parts of any product. When money is involved, users expect things to “just work.” If a payment succeeds but access is not granted, or a certificate is not generated, trust is broken instantly. On the other hand, if access is granted without a successful payment, the business loses revenue.
Now imagine this system for DevsCall. Learners complete a course, choose to purchase a certificate, pay through a gateway, and instantly receive a verifiable certificate with a QR code. This system must be secure, reliable, auditable, and resilient to retries and failures. This lesson walks through how to design such a system in a clean, production-ready way.
Payment unlocks entitlement
The first thing to get right is the mental model. You are not “selling a certificate PDF.” You are selling an entitlement.
The entitlement might be “Certificate for Course X for User Y.” Payment is just the mechanism that unlocks this entitlement. Once unlocked, everything else, certificate generation, download, verification, flows from it.
This separation makes the system much easier to reason about and debug.
Payment gateway integration: redirect, hosted, or embedded
Most platforms integrate with third-party payment gateways rather than handling cards themselves. This reduces compliance burden and security risk.
There are three common integration patterns. Redirect-based checkout sends users to the gateway’s hosted page. Embedded checkout keeps the user on your site but uses gateway-hosted components. Server-side payment intents create a transaction server-to-server and then confirm it on the client.
For learning platforms, hosted or embedded checkout is usually the best choice. You avoid handling sensitive card data and still control the user experience.
The important system design idea is this: your backend creates the payment intent and tracks its state. The frontend only initiates and confirms. The source of truth for payment status always lives on the server.
Webhooks: the only reliable confirmation
A classic beginner mistake is trusting the frontend redirect or success callback to confirm payment. That signal is not reliable. Browsers close, networks fail, users refresh.
The only reliable confirmation is the gateway webhook.
When a payment succeeds (or fails), the gateway sends a signed webhook request to your backend. Your system verifies the signature, parses the event, and updates payment state.
This means your system is eventually consistent: the user may see “processing” for a short time until the webhook arrives. That’s acceptable. Correctness matters more than instant gratification.
Idempotency: retries must be safe
Payments are retried. Webhooks are retried. Users click buttons twice.
Your system must be idempotent by design.
Every payment intent has a unique ID. Every webhook event has a unique event ID. Your database enforces that each payment and each event is processed once. If the same webhook arrives again, the system recognizes it and does nothing.
Idempotency is what separates a production-grade payment system from a fragile one. In interviews, explicitly calling this out is a strong signal.
Reconciliation: when things don’t line up
Even with good design, mismatches happen. A payment succeeds at the gateway, but your system crashes before processing the webhook. Or a webhook is delayed for hours.
To handle this, you need reconciliation.
Reconciliation is a background process that periodically queries the payment gateway for recent transactions and compares them with your database. Any missing or inconsistent records are corrected.
This is not about speed; it’s about financial correctness. Reconciliation jobs run quietly in the background and save you from rare but painful edge cases.
Certificate generation: asynchronous and deterministic
Once payment is confirmed, the entitlement is unlocked. Certificate generation should not happen inside the webhook handler. That would make the webhook slow and fragile.
Instead, payment confirmation emits an internal event like “certificate_entitlement_granted.” A background worker consumes this event and generates the certificate.
Certificate generation itself should be deterministic. Given user name, course name, completion date, and certificate ID, the output should always be the same. This allows safe retries without creating multiple certificates.
The generated certificate is stored securely (for example, in object storage), and its metadata is recorded in the database.
Verification and QR flow: trust at a glance
Certificates are only valuable if they can be verified.
Each certificate gets a globally unique certificate ID and a verification URL. A QR code encodes this URL and is embedded in the certificate.
When someone scans the QR code, they land on a verification page hosted by your platform. That page shows basic certificate details and verifies authenticity from your database. No sensitive user data is exposed, only what is needed to confirm validity.
This design makes certificates portable and trustworthy without requiring login.
Security considerations: assume attackers exist
Payment systems attract abuse. You must design defensively.
Webhook endpoints must verify signatures and reject unsigned or malformed requests. Payment records must never be updated directly from the client. Certificate generation must be gated strictly behind confirmed payment entitlements.
Fraud considerations include replay attacks, forged webhooks, and attempts to access certificate endpoints without authorization. Rate limiting, audit logs, and strict state transitions help mitigate these risks.
A good rule is this: every state change must be explainable later. If you can’t explain how a certificate was generated, the system is too loose.
Failure modes and graceful recovery
If the payment gateway is down, users should not be charged, and your system should clearly show “payment unavailable.” If webhooks are delayed, the user may wait, but the system eventually resolves correctly. If certificate generation fails, the entitlement remains, and the job retries.
Most importantly, no failure should result in “money taken, nothing delivered” without a clear recovery path.
Final thoughts
A payment and certificate system is not about fancy architecture. It’s about correctness, traceability, and trust.
In system design interviews, the strongest answers emphasize webhooks over callbacks, idempotency over speed, async processing over blocking flows, and verification over blind trust.
If you can explain how money flows, how state changes are validated, and how certificates can be proven authentic months later, you are thinking like a production engineer, not just a feature builder.
Frequently Asked Questions
Because payment confirmation can be delayed or retried. Decoupling ensures certificates are issued only after verified payment and can be safely retried.
Client-side callbacks are unreliable. Webhooks are the only trustworthy source of payment truth from the gateway.
Idempotency ensures repeated requests or webhooks do not create duplicate payments, entitlements, or certificates.
Reconciliation periodically compares gateway records with internal data to fix missed or inconsistent payment states.
Generating certificates can fail or take time. Async processing keeps webhooks fast and enables safe retries.
Each certificate includes a QR code that links to a verification page showing authenticity directly from the platform database.
Forged webhooks, replay attacks, unauthorized access, and fraud attempts. These are mitigated with signatures, strict state transitions, and audit logs.
Still have questions?Contact our support team