Overview
:::info Source
Sourced from services/marketplace-service/SERVICE_OVERVIEW.md in the documentation repo.
:::
Companion: 03 marketplace-service · 02 DDD · 04 Event-Driven · 13 Security
1. Purpose
The marketplace-service is the commercial heart of Ghasi-edTech. It owns the full lifecycle of a course as a purchasable product: provider listings, pricing plans, order placement, license issuance, seat allocation, coupon redemption, and revenue share / payout calculation. It is also the orchestrator of the Purchase Saga — a long-running, distributed workflow that threads billing, marketplace licensing, and enrollment into a single consistent business outcome.
Where catalog-service is the library of what exists, marketplace-service is the storefront of what can be bought, and on what terms.
2. Bounded Context
Marketplace — classified as Core (competitive differentiator).
The Marketplace context is a strategic investment, not a commodity. The two-sided network (providers selling, tenants buying), the revenue-share model, the refund-honesty policies, and the purchase-saga orchestration are the differentiating product surface. It must be owned in-house, custom-modeled, and evolved alongside the business strategy.
Relationships to neighbouring contexts:
| Neighbour | Pattern | Direction | Notes |
|---|---|---|---|
| Catalog | Customer / Supplier | Upstream | Listings reference CourseId + CourseVersionId; marketplace conforms to catalog's course identity. |
| Billing | Customer / ACL | Downstream for payment intents; Upstream for payment outcomes | Marketplace places payment intents via billing's API; consumes billing.payment.* events. ACL normalizes Stripe-specific concepts. |
| Enrollment | Customer / Supplier | Downstream | Saga step 4 requests enrollment-service to create an enrollment per seat. |
| Identity | Conformist | Upstream | Consumes JWT claims for buyer / provider auth. |
| Content | Customer | Downstream | Consumes content.play_package.built.v1 to mark a course version as purchasable (no purchase without a built play package). |
| Notification | Supplier | Downstream (event-driven) | Publishes purchase lifecycle events; notification-service emails receipts, license grants, refunds. |
| Analytics | Supplier | Downstream | Publishes order & license events; analytics builds GMV, conversion, cohort dashboards. |
3. Responsibilities
| Area | What Marketplace Owns |
|---|---|
| Listings | Draft → Submitted → Approved → Live → Suspended lifecycle; marketing copy, screenshots, featured assets |
| Pricing | Pricing plans (one-time, subscription, seat-pack, site-license), currency, perpetual offline flag, plan versioning |
| Orders | Order placement, totals calculation, tax reference (delegated to billing), status machine |
| Licenses | License issuance, seat accounting, validity windows, state (active / expired / revoked), source |
| Seat allocation | Org admins assigning/releasing seats; triggering enrollment creation or revocation |
| Coupons | Coupon codes, discount application, usage caps, tenant restrictions |
| Revenue share | 15% platform cut; provider earnings accrual; payout schedule computation |
| Purchase Saga | Owner of the marketplace.purchase saga; maintains saga state, drives compensation |
| Refunds (policy) | Enforces refund-deadline policy; triggers billing refund + license revocation |
| Provider onboarding (commercial side) | KYC status check; cannot go live without approved provider KYC (tenant-service owns the KYC record) |
| Marketplace browse API | Public listing discovery; search delegates to search-service, pricing + availability are marketplace-owned |
4. Non-Responsibilities
| Area | Owner | Why Not Marketplace |
|---|---|---|
| Payment processing, card tokenization, PCI scope | billing-service | Marketplace is PCI-out-of-scope by design. All card data lives inside billing's ACL to Stripe. |
| Tax calculation | billing-service | Tax rules are horizontally applicable; billing holds the tax engine / Stripe Tax integration. |
| Course content, versioning, publishing | catalog-service + authoring-service | Marketplace references CourseVersionId; does not model content. |
| Seat → learning progress tracking | enrollment-service + progress-service | Once a license seat is assigned, enrollment takes over. |
| KYC document handling | tenant-service | KYC is an org-level concern; marketplace reads a status flag only. |
| Notifications / email rendering | notification-service | Marketplace publishes events; rendering + delivery is elsewhere. |
| Analytics dashboards | analytics-service | Marketplace emits the facts; analytics computes and visualizes. |
5. Dependencies
5.1 Upstream Dependencies
| Dependency | Pattern | Purpose |
|---|---|---|
| catalog-service | CF (Customer) | Validate CourseId / CourseVersionId on listing creation; listen for catalog.course_version.published.v1 to allow listings to transition to live. |
| content-service | CF (Customer) | Listen for content.play_package.built.v1; a listing cannot go live until its course version has a built play package. |
| tenant-service | CF (Customer) | Provider KYC status (kyc_verified), buyer-tenant policies (e.g. purchase caps, allowed currencies). |
| billing-service | Customer / ACL | Places payment intents (POST /api/v1/payments/process); receives payment outcome events. |
| identity-service | Conformist | JWT validation and sub/tid claims for buyer/provider identification. |
5.2 Downstream Consumers
| Consumer | Pattern | What They Consume |
|---|---|---|
| enrollment-service | Event consumer (+ saga callback) | marketplace.license.granted.v1 — create enrollment per assigned seat |
| notification-service | Event consumer | marketplace.order.fulfilled.v1, .refunded.v1, .failed.v1 — email confirmations and refund notices |
| analytics-service | Event consumer | All marketplace.* events — GMV, conversion funnels, provider dashboards |
| search-service | Event consumer | marketplace.listing.approved.v1, .suspended.v1 — index mutation |
| billing-service | Event consumer | marketplace.order.placed.v1 — optional event sourcing for reconciliation |
| sync-service | Event consumer | marketplace.license.granted.v1 — ensures offline-bound devices know of new licensed courses |
5.3 Events Consumed by Marketplace
| Event | Producer | Purpose |
|---|---|---|
billing.payment.succeeded.v1 | billing-service | Saga step 2 → 3: move order from awaiting_payment to licensing |
billing.payment.failed.v1 | billing-service | Saga compensation: fail the order |
billing.payment.refunded.v1 | billing-service | Update order/license to refunded; revoke remaining seats |
enrollment.created.v1 | enrollment-service | Saga step 4 → 5: confirm seat fulfilled; advance order to fulfilled |
content.play_package.built.v1 | content-service | Mark listing as technically-shippable; allow transition to live |
catalog.course_version.published.v1 | catalog-service | Allow provider to list a specific course version |
gdpr.subject_request.received.v1 | cross-cutting (identity/tenant) | GDPR participation: anonymize/delete orders & licenses for the data subject |
6. Slice Involvement
| Slice | Scope | Milestone |
|---|---|---|
| S3 — Marketplace MVP | Listings, one-time pricing, basic purchase saga, refunds within window, SCORM 1.2 shipping through the saga | M2 |
| S5 — Subscription management | Subscription pricing plan kind, renewal-driven re-fulfilment, dunning-aware license state | M3 |
| S6 — White-label / multi-marketplace | Multi-currency listings, tenant-branded marketplace surfaces, provider payouts with FX | M4 |
7. Architectural Freeze Points
| Freeze | What Is Frozen | Frozen By |
|---|---|---|
| F03 | Branded value objects: ListingId, OrderId, LicenseId, PricingPlanId, CouponId | End of M1 |
| F01 | EventEnvelope schema — all events must conform | End of M0 (inherited) |
| F20 | Purchase Saga contract — step ordering, compensation rules, event shapes, timeouts — frozen at start of M2 | Start of M2 |
After F20, changes to saga event schemas require a versioned event (.v2), dual-publish for at least one minor release, and coordinated consumer updates.
8. Service Readiness Levels
| Level | Description | Target Milestone |
|---|---|---|
| L2 | Listings CRUD working, happy-path purchase saga, basic monitoring | M1 |
| L3 | Full saga with compensation, refund flow, contract tests for every event, SLO tracking | M2 |
| L4 | Subscription + multi-currency + payouts, chaos-tested saga, 99.95% availability SLO, security hardened | M4 |
9. Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────┐
│ API Gateway / Edge │
└──────────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────────────┐
│ marketplace-service (NestJS) │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Listings │ │ Orders │ │ Licenses │ │ Payouts │ │
│ │ Module │ │ Module │ │ Module │ │ Module │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ ┌─────▼───────────────▼────────────────▼────────────────▼──────────┐ │
│ │ Saga Orchestrator │ │
│ │ PurchaseSaga · state machine · compensation · timeouts │ │
│ └──────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼───────────────────────────────────────┐ │
│ │ Domain Layer (pure TS) │ │
│ │ Listing · Order · License · Coupon · PricingPlan · Revenue │ │
│ └──────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼───────────────────────────────────────┐ │
│ │ Infrastructure Layer │ │
│ │ Postgres (RLS) · NATS JetStream · Redis · OutboxRelay │ │
│ │ Stripe Connect (payouts) · Tax reference via billing ACL │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└────────────────┬──────────────────┬─────────────────┬────────────────────┘
│ │ │
┌─────────▼───┐ ┌─────────▼─────┐ ┌──────▼──────┐
│ Postgres │ │ NATS │ │ Redis │
│ (schema: │ │ JetStream │ │ (idempotency│
│ marketplace)│ │ │ │ + locks) │
└─────────────┘ └───────────────┘ └─────────────┘
10. Key Design Decisions
-
Marketplace owns the Purchase Saga, not billing. The business outcome is a fulfilled order (license + enrollment), not a successful payment. Payment is a necessary step, not the end goal. Therefore the domain that owns the end-to-end outcome (marketplace) owns the saga.
-
Orders are commercial, not financial. Orders carry line items, totals, and references to payments; they do not store tax calculations, payment methods, or invoice PDFs. Those are billing-owned.
-
Licenses are the durable artefact of a purchase. After refund windows close and saga state is archived, the license is the canonical record of what was bought. Enrollment consumes licenses; progress consumes enrollments.
-
Refund is a policy decision, not a payment operation. Marketplace enforces the
refundDeadlineand the "seats not yet consumed" rule; it then asks billing to perform the money movement. This keeps domain policy where it belongs. -
Platform take (15%) is modelled as a revenue-share value object. Provider earnings accrue per fulfilled order. Payouts are a batched derivation, not per-order money moves.
-
Providers cannot go live without KYC. This is enforced at listing approval time, not at purchase time, so buyers never see a listing they cannot legally pay for.
-
Listings reference an exact
CourseVersionId, not justCourseId. Publishing a new course version does not silently change what buyers purchased; the listing must be explicitly re-targeted by the provider. -
PCI is zero in marketplace. Marketplace never sees a PAN, never sees a tokenized card, never stores a payment method. The boundary is strict.