Skip to main content

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:

NeighbourPatternDirectionNotes
CatalogCustomer / SupplierUpstreamListings reference CourseId + CourseVersionId; marketplace conforms to catalog's course identity.
BillingCustomer / ACLDownstream for payment intents; Upstream for payment outcomesMarketplace places payment intents via billing's API; consumes billing.payment.* events. ACL normalizes Stripe-specific concepts.
EnrollmentCustomer / SupplierDownstreamSaga step 4 requests enrollment-service to create an enrollment per seat.
IdentityConformistUpstreamConsumes JWT claims for buyer / provider auth.
ContentCustomerDownstreamConsumes content.play_package.built.v1 to mark a course version as purchasable (no purchase without a built play package).
NotificationSupplierDownstream (event-driven)Publishes purchase lifecycle events; notification-service emails receipts, license grants, refunds.
AnalyticsSupplierDownstreamPublishes order & license events; analytics builds GMV, conversion, cohort dashboards.

3. Responsibilities

AreaWhat Marketplace Owns
ListingsDraft → Submitted → Approved → Live → Suspended lifecycle; marketing copy, screenshots, featured assets
PricingPricing plans (one-time, subscription, seat-pack, site-license), currency, perpetual offline flag, plan versioning
OrdersOrder placement, totals calculation, tax reference (delegated to billing), status machine
LicensesLicense issuance, seat accounting, validity windows, state (active / expired / revoked), source
Seat allocationOrg admins assigning/releasing seats; triggering enrollment creation or revocation
CouponsCoupon codes, discount application, usage caps, tenant restrictions
Revenue share15% platform cut; provider earnings accrual; payout schedule computation
Purchase SagaOwner 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 APIPublic listing discovery; search delegates to search-service, pricing + availability are marketplace-owned

4. Non-Responsibilities

AreaOwnerWhy Not Marketplace
Payment processing, card tokenization, PCI scopebilling-serviceMarketplace is PCI-out-of-scope by design. All card data lives inside billing's ACL to Stripe.
Tax calculationbilling-serviceTax rules are horizontally applicable; billing holds the tax engine / Stripe Tax integration.
Course content, versioning, publishingcatalog-service + authoring-serviceMarketplace references CourseVersionId; does not model content.
Seat → learning progress trackingenrollment-service + progress-serviceOnce a license seat is assigned, enrollment takes over.
KYC document handlingtenant-serviceKYC is an org-level concern; marketplace reads a status flag only.
Notifications / email renderingnotification-serviceMarketplace publishes events; rendering + delivery is elsewhere.
Analytics dashboardsanalytics-serviceMarketplace emits the facts; analytics computes and visualizes.

5. Dependencies

5.1 Upstream Dependencies

DependencyPatternPurpose
catalog-serviceCF (Customer)Validate CourseId / CourseVersionId on listing creation; listen for catalog.course_version.published.v1 to allow listings to transition to live.
content-serviceCF (Customer)Listen for content.play_package.built.v1; a listing cannot go live until its course version has a built play package.
tenant-serviceCF (Customer)Provider KYC status (kyc_verified), buyer-tenant policies (e.g. purchase caps, allowed currencies).
billing-serviceCustomer / ACLPlaces payment intents (POST /api/v1/payments/process); receives payment outcome events.
identity-serviceConformistJWT validation and sub/tid claims for buyer/provider identification.

5.2 Downstream Consumers

ConsumerPatternWhat They Consume
enrollment-serviceEvent consumer (+ saga callback)marketplace.license.granted.v1 — create enrollment per assigned seat
notification-serviceEvent consumermarketplace.order.fulfilled.v1, .refunded.v1, .failed.v1 — email confirmations and refund notices
analytics-serviceEvent consumerAll marketplace.* events — GMV, conversion funnels, provider dashboards
search-serviceEvent consumermarketplace.listing.approved.v1, .suspended.v1 — index mutation
billing-serviceEvent consumermarketplace.order.placed.v1 — optional event sourcing for reconciliation
sync-serviceEvent consumermarketplace.license.granted.v1 — ensures offline-bound devices know of new licensed courses

5.3 Events Consumed by Marketplace

EventProducerPurpose
billing.payment.succeeded.v1billing-serviceSaga step 2 → 3: move order from awaiting_payment to licensing
billing.payment.failed.v1billing-serviceSaga compensation: fail the order
billing.payment.refunded.v1billing-serviceUpdate order/license to refunded; revoke remaining seats
enrollment.created.v1enrollment-serviceSaga step 4 → 5: confirm seat fulfilled; advance order to fulfilled
content.play_package.built.v1content-serviceMark listing as technically-shippable; allow transition to live
catalog.course_version.published.v1catalog-serviceAllow provider to list a specific course version
gdpr.subject_request.received.v1cross-cutting (identity/tenant)GDPR participation: anonymize/delete orders & licenses for the data subject

6. Slice Involvement

SliceScopeMilestone
S3 — Marketplace MVPListings, one-time pricing, basic purchase saga, refunds within window, SCORM 1.2 shipping through the sagaM2
S5 — Subscription managementSubscription pricing plan kind, renewal-driven re-fulfilment, dunning-aware license stateM3
S6 — White-label / multi-marketplaceMulti-currency listings, tenant-branded marketplace surfaces, provider payouts with FXM4

7. Architectural Freeze Points

FreezeWhat Is FrozenFrozen By
F03Branded value objects: ListingId, OrderId, LicenseId, PricingPlanId, CouponIdEnd of M1
F01EventEnvelope schema — all events must conformEnd of M0 (inherited)
F20Purchase Saga contract — step ordering, compensation rules, event shapes, timeouts — frozen at start of M2Start 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

LevelDescriptionTarget Milestone
L2Listings CRUD working, happy-path purchase saga, basic monitoringM1
L3Full saga with compensation, refund flow, contract tests for every event, SLO trackingM2
L4Subscription + multi-currency + payouts, chaos-tested saga, 99.95% availability SLO, security hardenedM4

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

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

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

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

  4. Refund is a policy decision, not a payment operation. Marketplace enforces the refundDeadline and the "seats not yet consumed" rule; it then asks billing to perform the money movement. This keeps domain policy where it belongs.

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

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

  7. Listings reference an exact CourseVersionId, not just CourseId. Publishing a new course version does not silently change what buyers purchased; the listing must be explicitly re-targeted by the provider.

  8. PCI is zero in marketplace. Marketplace never sees a PAN, never sees a tokenized card, never stores a payment method. The boundary is strict.