marketplace

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package marketplace provides a unified framework for two-sided marketplace applications including listings, licenses, and subscriptions.

Index

Constants

View Source
const (
	ResourceTypeListing      = "listing"
	ResourceTypeLicense      = "license"
	ResourceTypeSubscription = "subscription"
	ResourceTypeOrganization = "organization"
	ResourceTypeCreatorOrg   = "creator_org"
	ResourceTypePrincipal    = "principal"
)

SpiceDB resource types for marketplace entities.

View Source
const (
	RelationCreatorOrg   = "creator_org"
	RelationOwner        = "owner"
	RelationLicensedOrg  = "licensed_org"
	RelationListing      = "listing"
	RelationOrganization = "organization"
	RelationPurchasedBy  = "purchased_by"
	RelationSeatHolder   = "seat_holder"
	RelationSubscriber   = "subscriber"
)

SpiceDB relations for marketplace entities.

View Source
const (
	PlanTierFree       = "free"
	PlanTierStarter    = "starter"
	PlanTierPro        = "pro"
	PlanTierEnterprise = "enterprise"
)

PlanTier constants for common subscription tiers.

Variables

View Source
var (
	// ErrListingNotFound is returned when a listing cannot be found.
	ErrListingNotFound = errors.New("listing not found")

	// ErrListingNotPublished is returned when accessing an unpublished listing.
	ErrListingNotPublished = errors.New("listing not published")

	// ErrLicenseNotFound is returned when a license cannot be found.
	ErrLicenseNotFound = errors.New("license not found")

	// ErrLicenseExpired is returned when a license has expired.
	ErrLicenseExpired = errors.New("license expired")

	// ErrLicenseNotYetValid is returned when a license hasn't started.
	ErrLicenseNotYetValid = errors.New("license not yet valid")

	// ErrNoSeatsAvailable is returned when all seats are assigned.
	ErrNoSeatsAvailable = errors.New("no seats available")

	// ErrSeatAlreadyAssigned is returned when a user already has a seat.
	ErrSeatAlreadyAssigned = errors.New("seat already assigned to this user")

	// ErrSeatNotAssigned is returned when trying to unassign a non-existent seat.
	ErrSeatNotAssigned = errors.New("seat not assigned to this user")

	// ErrSubscriptionNotFound is returned when a subscription cannot be found.
	ErrSubscriptionNotFound = errors.New("subscription not found")

	// ErrSubscriptionInactive is returned when a subscription is not active.
	ErrSubscriptionInactive = errors.New("subscription not active")

	// ErrInvalidRevenueShare is returned when revenue share doesn't add to 100%.
	ErrInvalidRevenueShare = errors.New("revenue share must add up to 100%")

	// ErrInvalidPricingModel is returned for unknown pricing models.
	ErrInvalidPricingModel = errors.New("invalid pricing model")

	// ErrInvalidLicenseType is returned for unknown license types.
	ErrInvalidLicenseType = errors.New("invalid license type")

	// ErrAlreadyLicensed is returned when an org already has a license.
	ErrAlreadyLicensed = errors.New("organization already licensed for this listing")

	// ErrCannotPurchaseOwnListing is returned when a creator tries to purchase their own listing.
	ErrCannotPurchaseOwnListing = errors.New("cannot purchase your own listing")

	// ErrPaymentRequired is returned when payment is required but not provided.
	ErrPaymentRequired = errors.New("payment required")

	// ErrPaymentFailed is returned when payment processing fails.
	ErrPaymentFailed = errors.New("payment failed")
)

Sentinel errors for marketplace operations.

View Source
var MarketplaceSchema string

Functions

func MergeSchema

func MergeSchema(appSchema string) string

MergeSchema combines the marketplace schema with an application-specific schema. The app schema should define app-specific resource types that reference marketplace types (listing, license, etc.).

func PlanTiers

func PlanTiers() []string

PlanTiers returns all standard plan tiers.

Types

type API

type API struct {
	// contains filtered or unexported fields
}

API handles marketplace HTTP endpoints.

func NewAPI

func NewAPI(humaAPI huma.API, cfg APIConfig, svc Service) *API

NewAPI creates a new marketplace API handler.

type APIConfig

type APIConfig struct {
	// BasePath is the API base path (e.g., "/api/v1").
	BasePath string
}

APIConfig holds API configuration.

type ArchiveListingInput

type ArchiveListingInput struct {
	ID string `path:"id" format:"uuid"`
}

ArchiveListingInput is the request for archiving a listing.

type ArchiveListingOutput

type ArchiveListingOutput struct {
	Body *ListingResponse
}

ArchiveListingOutput is the response for archiving a listing.

type AssignSeatInput

type AssignSeatInput struct {
	LicenseID string `path:"license_id" format:"uuid"`
	Body      struct {
		PrincipalID string `json:"principal_id" required:"true" format:"uuid"`
		AssignedBy  string `json:"assigned_by" required:"true" format:"uuid"`
	}
}

AssignSeatInput is the request for assigning a seat.

type AssignSeatOutput

type AssignSeatOutput struct {
	Body *SeatAssignmentResponse
}

AssignSeatOutput is the response for assigning a seat.

type AuthzSyncer

type AuthzSyncer interface {
	// SyncListing syncs a listing to the authorization system.
	SyncListing(ctx context.Context, listing *Listing) error

	// SyncLicense syncs a license grant to the authorization system.
	SyncLicense(ctx context.Context, license *License) error

	// SyncLicenseRevocation syncs a license revocation to the authorization system.
	SyncLicenseRevocation(ctx context.Context, license *License) error

	// SyncSeatAssignment syncs a seat assignment to the authorization system.
	SyncSeatAssignment(ctx context.Context, assignment *SeatAssignment) error

	// SyncSeatUnassignment syncs a seat removal to the authorization system.
	SyncSeatUnassignment(ctx context.Context, licenseID, principalID uuid.UUID) error
}

AuthzSyncer syncs marketplace entities to the authorization system.

type CheckoutRequest

type CheckoutRequest struct {
	// ListingID is the listing to purchase.
	ListingID uuid.UUID

	// OrganizationID is the purchasing organization.
	OrganizationID uuid.UUID

	// PurchaserID is the principal making the purchase.
	PurchaserID uuid.UUID

	// Seats is the number of seats (for per-seat pricing).
	Seats *int

	// SuccessURL is the redirect URL on success.
	SuccessURL string

	// CancelURL is the redirect URL on cancel.
	CancelURL string
}

CheckoutRequest contains parameters for creating a checkout session.

type CheckoutService

type CheckoutService interface {
	// CreateCheckoutSession creates a Stripe checkout session for a listing.
	CreateCheckoutSession(ctx context.Context, req CheckoutRequest) (*CheckoutSession, error)

	// ProcessWebhook handles Stripe webhook events.
	ProcessWebhook(ctx context.Context, payload []byte, signature string) error
}

CheckoutService provides operations for purchasing licenses.

type CheckoutSession

type CheckoutSession struct {
	// SessionID is the Stripe checkout session ID.
	SessionID string

	// URL is the checkout URL to redirect the user to.
	URL string
}

CheckoutSession contains the result of creating a checkout session.

type CreateCheckoutInput

type CreateCheckoutInput struct {
	Body struct {
		ListingID      string `json:"listing_id" required:"true" format:"uuid"`
		OrganizationID string `json:"organization_id" required:"true" format:"uuid"`
		PurchaserID    string `json:"purchaser_id" required:"true" format:"uuid"`
		Seats          *int   `json:"seats,omitempty" minimum:"1"`
		SuccessURL     string `json:"success_url" required:"true" format:"uri"`
		CancelURL      string `json:"cancel_url" required:"true" format:"uri"`
	}
}

CreateCheckoutInput is the request for creating a checkout session.

type CreateCheckoutOutput

type CreateCheckoutOutput struct {
	Body struct {
		SessionID string `json:"session_id"`
		URL       string `json:"url"`
	}
}

CreateCheckoutOutput is the response for creating a checkout session.

type CreateListingInput

type CreateListingInput struct {
	Body struct {
		CreatorOrgID string         `json:"creator_org_id" required:"true" format:"uuid"`
		OwnerID      string         `json:"owner_id" required:"true" format:"uuid"`
		ProductType  string         `json:"product_type" required:"true" minLength:"1" maxLength:"50"`
		ProductID    *string        `json:"product_id,omitempty" format:"uuid"`
		Title        string         `json:"title" required:"true" minLength:"1" maxLength:"200"`
		Description  string         `json:"description,omitempty" maxLength:"5000"`
		PricingModel string         `json:"pricing_model" required:"true" enum:"free,one_time,subscription,per_seat"`
		PriceCents   int64          `json:"price_cents" minimum:"0"`
		Currency     string         `json:"currency" required:"true" minLength:"3" maxLength:"3" default:"USD"`
		Metadata     map[string]any `json:"metadata,omitempty"`
	}
}

CreateListingInput is the request for creating a listing.

type CreateListingOutput

type CreateListingOutput struct {
	Body *ListingResponse
}

CreateListingOutput is the response for creating a listing.

type DeleteListingInput

type DeleteListingInput struct {
	ID string `path:"id" format:"uuid"`
}

DeleteListingInput is the request for deleting a listing.

type EntLicenseService

type EntLicenseService struct {
	// contains filtered or unexported fields
}

EntLicenseService is an Ent-backed implementation of LicenseService.

func NewEntLicenseService

func NewEntLicenseService(client *ent.Client, authzSync AuthzSyncer) *EntLicenseService

NewEntLicenseService creates a new Ent-backed license service.

func (*EntLicenseService) AssignSeat

func (s *EntLicenseService) AssignSeat(ctx context.Context, assignment *SeatAssignment) error

AssignSeat assigns a user to a license seat.

func (*EntLicenseService) Check

func (s *EntLicenseService) Check(ctx context.Context, listingID, orgID uuid.UUID) (bool, error)

Check checks if an organization has a valid license for a listing.

func (*EntLicenseService) CheckPrincipal

func (s *EntLicenseService) CheckPrincipal(ctx context.Context, listingID, principalID uuid.UUID) (bool, error)

CheckPrincipal checks if a principal has access via license.

func (*EntLicenseService) Get

func (s *EntLicenseService) Get(ctx context.Context, id uuid.UUID) (*License, error)

Get retrieves a license by ID.

func (*EntLicenseService) GetByListingAndOrg

func (s *EntLicenseService) GetByListingAndOrg(ctx context.Context, listingID, orgID uuid.UUID) (*License, error)

GetByListingAndOrg retrieves a license for a specific listing and organization.

func (*EntLicenseService) Grant

func (s *EntLicenseService) Grant(ctx context.Context, l *License) error

Grant creates a new license for an organization.

func (*EntLicenseService) List

func (s *EntLicenseService) List(ctx context.Context, orgID uuid.UUID) ([]*License, error)

List retrieves licenses for an organization.

func (*EntLicenseService) ListByListing

func (s *EntLicenseService) ListByListing(ctx context.Context, listingID uuid.UUID) ([]*License, error)

ListByListing retrieves all licenses for a listing.

func (*EntLicenseService) ListSeatAssignments

func (s *EntLicenseService) ListSeatAssignments(ctx context.Context, licenseID uuid.UUID) ([]*SeatAssignment, error)

ListSeatAssignments retrieves all seat assignments for a license.

func (*EntLicenseService) Revoke

func (s *EntLicenseService) Revoke(ctx context.Context, id uuid.UUID) error

Revoke revokes a license, removing access.

func (*EntLicenseService) UnassignSeat

func (s *EntLicenseService) UnassignSeat(ctx context.Context, licenseID, principalID uuid.UUID) error

UnassignSeat removes a user from a license seat.

func (*EntLicenseService) Update

func (s *EntLicenseService) Update(ctx context.Context, l *License) error

Update updates a license's details.

type EntListingService

type EntListingService struct {
	// contains filtered or unexported fields
}

EntListingService is an Ent-backed implementation of ListingService.

func NewEntListingService

func NewEntListingService(client *ent.Client, authzSync AuthzSyncer) *EntListingService

NewEntListingService creates a new Ent-backed listing service.

func (*EntListingService) Archive

func (s *EntListingService) Archive(ctx context.Context, id uuid.UUID) error

Archive archives a listing, removing it from the marketplace.

func (*EntListingService) Create

func (s *EntListingService) Create(ctx context.Context, l *Listing) error

Create creates a new listing in draft status.

func (*EntListingService) Delete

func (s *EntListingService) Delete(ctx context.Context, id uuid.UUID) error

Delete removes a listing (must be draft or archived).

func (*EntListingService) Get

func (s *EntListingService) Get(ctx context.Context, id uuid.UUID) (*Listing, error)

Get retrieves a listing by ID.

func (*EntListingService) GetByProduct

func (s *EntListingService) GetByProduct(ctx context.Context, productType string, productID uuid.UUID) (*Listing, error)

GetByProduct retrieves a listing by product type and ID.

func (*EntListingService) List

List retrieves listings with optional filters.

func (*EntListingService) ListByCreator

func (s *EntListingService) ListByCreator(ctx context.Context, creatorOrgID uuid.UUID) ([]*Listing, error)

ListByCreator retrieves all listings for a creator organization.

func (*EntListingService) Publish

func (s *EntListingService) Publish(ctx context.Context, id uuid.UUID) error

Publish publishes a listing to the marketplace.

func (*EntListingService) Update

func (s *EntListingService) Update(ctx context.Context, l *Listing) error

Update updates a listing's details.

type EntService

type EntService struct {
	// contains filtered or unexported fields
}

EntService is the Ent-backed implementation of the marketplace Service interface.

func NewEntService

func NewEntService(client *ent.Client, authzSync AuthzSyncer) *EntService

NewEntService creates a new Ent-backed marketplace service. The authzSync parameter is optional and can be nil if authorization sync is not needed.

func (*EntService) Checkout

func (s *EntService) Checkout() CheckoutService

Checkout returns the checkout service.

func (*EntService) Licenses

func (s *EntService) Licenses() LicenseService

Licenses returns the license service.

func (*EntService) Listings

func (s *EntService) Listings() ListingService

Listings returns the listing service.

func (*EntService) Subscriptions

func (s *EntService) Subscriptions() SubscriptionService

Subscriptions returns the subscription service.

func (*EntService) WithCheckout

func (s *EntService) WithCheckout(checkout CheckoutService) *EntService

WithCheckout sets the checkout service.

type EntSubscriptionService

type EntSubscriptionService struct {
	// contains filtered or unexported fields
}

EntSubscriptionService is an Ent-backed implementation of SubscriptionService.

func NewEntSubscriptionService

func NewEntSubscriptionService(client *ent.Client) *EntSubscriptionService

NewEntSubscriptionService creates a new Ent-backed subscription service.

func (*EntSubscriptionService) Cancel

func (s *EntSubscriptionService) Cancel(ctx context.Context, id uuid.UUID) error

Cancel cancels a subscription at period end.

func (*EntSubscriptionService) CancelImmediately

func (s *EntSubscriptionService) CancelImmediately(ctx context.Context, id uuid.UUID) error

CancelImmediately cancels a subscription immediately.

func (*EntSubscriptionService) ChangePlan

func (s *EntSubscriptionService) ChangePlan(ctx context.Context, id uuid.UUID, newPlan string) error

ChangePlan changes the subscription plan.

func (*EntSubscriptionService) Create

Create creates a new subscription.

func (*EntSubscriptionService) Get

Get retrieves a subscription by ID.

func (*EntSubscriptionService) GetByOrg

func (s *EntSubscriptionService) GetByOrg(ctx context.Context, orgID uuid.UUID) (*Subscription, error)

GetByOrg retrieves the subscription for an organization.

func (*EntSubscriptionService) GetPlanTier

func (s *EntSubscriptionService) GetPlanTier(ctx context.Context, orgID uuid.UUID) (string, error)

GetPlanTier returns the current plan tier for an organization.

func (*EntSubscriptionService) IsActive

func (s *EntSubscriptionService) IsActive(ctx context.Context, orgID uuid.UUID) (bool, error)

IsActive checks if an organization has an active subscription.

func (*EntSubscriptionService) Reactivate

func (s *EntSubscriptionService) Reactivate(ctx context.Context, id uuid.UUID) error

Reactivate reactivates a canceled subscription.

func (*EntSubscriptionService) Update

Update updates a subscription's details.

type GetLicenseInput

type GetLicenseInput struct {
	ID string `path:"id" format:"uuid"`
}

GetLicenseInput is the request for getting a license.

type GetLicenseOutput

type GetLicenseOutput struct {
	Body *LicenseResponse
}

GetLicenseOutput is the response for getting a license.

type GetListingInput

type GetListingInput struct {
	ID string `path:"id" format:"uuid"`
}

GetListingInput is the request for getting a listing.

type GetListingOutput

type GetListingOutput struct {
	Body *ListingResponse
}

GetListingOutput is the response for getting a listing.

type License

type License struct {
	// ID is the unique identifier for this license.
	ID uuid.UUID `json:"id"`

	// ListingID references the marketplace listing.
	ListingID uuid.UUID `json:"listingId"`

	// OrganizationID is the organization that holds this license.
	OrganizationID uuid.UUID `json:"organizationId"`

	// LicenseType defines the scope of access.
	LicenseType LicenseType `json:"licenseType"`

	// Seats is the number of allowed users (nil for unlimited).
	Seats *int `json:"seats,omitempty"`

	// UsedSeats is the current number of assigned users.
	UsedSeats int `json:"usedSeats"`

	// ValidFrom is when the license becomes active.
	ValidFrom time.Time `json:"validFrom"`

	// ValidUntil is when the license expires (nil for perpetual).
	ValidUntil *time.Time `json:"validUntil,omitempty"`

	// StripeSubscriptionID is the Stripe subscription (for recurring).
	StripeSubscriptionID *string `json:"stripeSubscriptionId,omitempty"`

	// PurchasedBy is the principal who purchased this license.
	PurchasedBy uuid.UUID `json:"purchasedBy"`

	// CreatedAt is when the license was created.
	CreatedAt time.Time `json:"createdAt"`

	// UpdatedAt is when the license was last modified.
	UpdatedAt time.Time `json:"updatedAt"`
}

License represents an entitlement to use a product.

func (*License) HasAvailableSeats

func (l *License) HasAvailableSeats() bool

HasAvailableSeats returns true if seats are available for assignment.

func (*License) IsValid

func (l *License) IsValid() bool

IsValid returns true if the license is currently valid.

func (*License) SeatsRemaining

func (l *License) SeatsRemaining() int

SeatsRemaining returns the number of seats available. Returns -1 for unlimited licenses.

type LicenseResponse

type LicenseResponse struct {
	ID                   string     `json:"id"`
	ListingID            string     `json:"listing_id"`
	OrganizationID       string     `json:"organization_id"`
	LicenseType          string     `json:"license_type"`
	Seats                *int       `json:"seats,omitempty"`
	UsedSeats            int        `json:"used_seats"`
	ValidFrom            time.Time  `json:"valid_from"`
	ValidUntil           *time.Time `json:"valid_until,omitempty"`
	StripeSubscriptionID *string    `json:"stripe_subscription_id,omitempty"`
	PurchasedBy          string     `json:"purchased_by"`
	CreatedAt            time.Time  `json:"created_at"`
	UpdatedAt            time.Time  `json:"updated_at"`
}

LicenseResponse is the API representation of a license.

type LicenseService

type LicenseService interface {
	// Grant creates a new license for an organization.
	Grant(ctx context.Context, license *License) error

	// Get retrieves a license by ID.
	Get(ctx context.Context, id uuid.UUID) (*License, error)

	// GetByListingAndOrg retrieves a license for a specific listing and organization.
	GetByListingAndOrg(ctx context.Context, listingID, orgID uuid.UUID) (*License, error)

	// Update updates a license's details.
	Update(ctx context.Context, license *License) error

	// Revoke revokes a license, removing access.
	Revoke(ctx context.Context, id uuid.UUID) error

	// Check checks if an organization has a valid license for a listing.
	Check(ctx context.Context, listingID, orgID uuid.UUID) (bool, error)

	// CheckPrincipal checks if a principal has access via license.
	CheckPrincipal(ctx context.Context, listingID, principalID uuid.UUID) (bool, error)

	// List retrieves licenses for an organization.
	List(ctx context.Context, orgID uuid.UUID) ([]*License, error)

	// ListByListing retrieves all licenses for a listing.
	ListByListing(ctx context.Context, listingID uuid.UUID) ([]*License, error)

	// AssignSeat assigns a user to a license seat.
	AssignSeat(ctx context.Context, assignment *SeatAssignment) error

	// UnassignSeat removes a user from a license seat.
	UnassignSeat(ctx context.Context, licenseID, principalID uuid.UUID) error

	// ListSeatAssignments retrieves all seat assignments for a license.
	ListSeatAssignments(ctx context.Context, licenseID uuid.UUID) ([]*SeatAssignment, error)
}

LicenseService provides operations for licenses and entitlements.

type LicenseType

type LicenseType string

LicenseType defines the scope of a license.

const (
	// LicenseSeatBased grants access to a specific number of users.
	LicenseSeatBased LicenseType = "seat_based"

	// LicenseTeam grants access to all members of a team/group.
	LicenseTeam LicenseType = "team"

	// LicenseUnlimited grants unlimited access within an organization.
	LicenseUnlimited LicenseType = "unlimited"
)

func LicenseTypes

func LicenseTypes() []LicenseType

LicenseTypes returns all valid license types.

type ListLicensesInput

type ListLicensesInput struct {
	OrganizationID string `query:"organization_id" required:"true" format:"uuid"`
}

ListLicensesInput is the request for listing licenses.

type ListLicensesOutput

type ListLicensesOutput struct {
	Body struct {
		Licenses []*LicenseResponse `json:"licenses"`
	}
}

ListLicensesOutput is the response for listing licenses.

type ListListingsInput

type ListListingsInput struct {
	Status       *string `query:"status" enum:"draft,pending_review,published,archived"`
	ProductType  *string `query:"product_type"`
	CreatorOrgID *string `query:"creator_org_id" format:"uuid"`
	Published    *bool   `query:"published"`
	Limit        int     `query:"limit" default:"20" minimum:"1" maximum:"100"`
	Offset       int     `query:"offset" default:"0" minimum:"0"`
}

ListListingsInput is the request for listing listings.

type ListListingsOptions

type ListListingsOptions struct {
	// Status filters by listing status.
	Status *ListingStatus

	// ProductType filters by product type.
	ProductType *string

	// CreatorOrgID filters by creator organization.
	CreatorOrgID *uuid.UUID

	// PublishedOnly returns only published listings.
	PublishedOnly bool

	// Limit is the maximum number of results.
	Limit int

	// Offset is the pagination offset.
	Offset int

	// OrderBy specifies the sort order.
	OrderBy string
}

ListListingsOptions configures listing queries.

type ListListingsOutput

type ListListingsOutput struct {
	Body struct {
		Listings []*ListingResponse `json:"listings"`
		Total    int                `json:"total"`
	}
}

ListListingsOutput is the response for listing listings.

type ListSeatsInput

type ListSeatsInput struct {
	LicenseID string `path:"license_id" format:"uuid"`
}

ListSeatsInput is the request for listing seat assignments.

type ListSeatsOutput

type ListSeatsOutput struct {
	Body struct {
		Seats          []*SeatAssignmentResponse `json:"seats"`
		TotalSeats     *int                      `json:"total_seats,omitempty"`
		UsedSeats      int                       `json:"used_seats"`
		AvailableSeats int                       `json:"available_seats"`
	}
}

ListSeatsOutput is the response for listing seat assignments.

type Listing

type Listing struct {
	// ID is the unique identifier for this listing.
	ID uuid.UUID `json:"id"`

	// CreatorOrgID is the organization that created this listing.
	CreatorOrgID uuid.UUID `json:"creatorOrgId"`

	// OwnerID is the principal who owns this listing.
	OwnerID uuid.UUID `json:"ownerId"`

	// ProductType identifies the type of product (app-specific).
	// Examples: "course", "dashboard_template", "data_connector"
	ProductType string `json:"productType"`

	// ProductID references the actual product in the app's domain.
	ProductID uuid.UUID `json:"productId"`

	// Title is the display name for the listing.
	Title string `json:"title"`

	// Description is the detailed description of the product.
	Description string `json:"description,omitempty"`

	// PricingModel defines how the product is priced.
	PricingModel PricingModel `json:"pricingModel"`

	// PriceCents is the price in cents (0 for free).
	PriceCents int64 `json:"priceCents"`

	// Currency is the ISO 4217 currency code.
	Currency string `json:"currency"`

	// Status is the publication state.
	Status ListingStatus `json:"status"`

	// Metadata contains app-specific data.
	Metadata map[string]any `json:"metadata,omitempty"`

	// CreatedAt is when the listing was created.
	CreatedAt time.Time `json:"createdAt"`

	// UpdatedAt is when the listing was last modified.
	UpdatedAt time.Time `json:"updatedAt"`

	// PublishedAt is when the listing was published (nil if not published).
	PublishedAt *time.Time `json:"publishedAt,omitempty"`
}

Listing represents a product available on the marketplace.

func (*Listing) IsFree

func (l *Listing) IsFree() bool

IsFree returns true if the listing has no cost.

func (*Listing) IsPublished

func (l *Listing) IsPublished() bool

IsPublished returns true if the listing is live on the marketplace.

type ListingResponse

type ListingResponse struct {
	ID           string         `json:"id"`
	CreatorOrgID string         `json:"creator_org_id"`
	OwnerID      string         `json:"owner_id"`
	ProductType  string         `json:"product_type"`
	ProductID    *string        `json:"product_id,omitempty"`
	Title        string         `json:"title"`
	Description  string         `json:"description,omitempty"`
	PricingModel string         `json:"pricing_model"`
	PriceCents   int64          `json:"price_cents"`
	Currency     string         `json:"currency"`
	Status       string         `json:"status"`
	Metadata     map[string]any `json:"metadata,omitempty"`
	CreatedAt    time.Time      `json:"created_at"`
	UpdatedAt    time.Time      `json:"updated_at"`
	PublishedAt  *time.Time     `json:"published_at,omitempty"`
}

ListingResponse is the API representation of a listing.

type ListingService

type ListingService interface {
	// Create creates a new listing in draft status.
	Create(ctx context.Context, listing *Listing) error

	// Get retrieves a listing by ID.
	Get(ctx context.Context, id uuid.UUID) (*Listing, error)

	// GetByProduct retrieves a listing by product type and ID.
	GetByProduct(ctx context.Context, productType string, productID uuid.UUID) (*Listing, error)

	// Update updates a listing's details.
	Update(ctx context.Context, listing *Listing) error

	// Delete removes a listing (must be draft or archived).
	Delete(ctx context.Context, id uuid.UUID) error

	// Publish publishes a listing to the marketplace.
	Publish(ctx context.Context, id uuid.UUID) error

	// Archive archives a listing, removing it from the marketplace.
	Archive(ctx context.Context, id uuid.UUID) error

	// List retrieves listings with optional filters.
	List(ctx context.Context, opts ListListingsOptions) ([]*Listing, error)

	// ListByCreator retrieves all listings for a creator organization.
	ListByCreator(ctx context.Context, creatorOrgID uuid.UUID) ([]*Listing, error)
}

ListingService provides operations for marketplace listings.

type ListingStatus

type ListingStatus string

ListingStatus represents the publication state of a listing.

const (
	// ListingStatusDraft indicates the listing is being prepared.
	ListingStatusDraft ListingStatus = "draft"

	// ListingStatusPendingReview indicates the listing is awaiting approval.
	ListingStatusPendingReview ListingStatus = "pending_review"

	// ListingStatusPublished indicates the listing is live on the marketplace.
	ListingStatusPublished ListingStatus = "published"

	// ListingStatusArchived indicates the listing has been retired.
	ListingStatusArchived ListingStatus = "archived"
)

func ListingStatuses

func ListingStatuses() []ListingStatus

ListingStatuses returns all valid listing statuses.

type PricingModel

type PricingModel string

PricingModel defines how a product is priced.

const (
	// PricingFree indicates no charge (may require attribution).
	PricingFree PricingModel = "free"

	// PricingOneTime indicates a single purchase for perpetual access.
	PricingOneTime PricingModel = "one_time"

	// PricingSubscription indicates recurring monthly/annual billing.
	PricingSubscription PricingModel = "subscription"

	// PricingPerSeat indicates per-user pricing within an organization.
	PricingPerSeat PricingModel = "per_seat"
)

func PricingModels

func PricingModels() []PricingModel

PricingModels returns all valid pricing models.

type PublishListingInput

type PublishListingInput struct {
	ID string `path:"id" format:"uuid"`
}

PublishListingInput is the request for publishing a listing.

type PublishListingOutput

type PublishListingOutput struct {
	Body *ListingResponse
}

PublishListingOutput is the response for publishing a listing.

type RevenueShare

type RevenueShare struct {
	// CreatorPercent is the percentage going to the creator (0-100).
	CreatorPercent int `json:"creatorPercent"`

	// PlatformPercent is the percentage going to the platform (0-100).
	PlatformPercent int `json:"platformPercent"`
}

RevenueShare defines the revenue split for marketplace sales.

func DefaultRevenueShare

func DefaultRevenueShare() RevenueShare

DefaultRevenueShare returns the default revenue split (70/30).

func (RevenueShare) Validate

func (r RevenueShare) Validate() error

Validate checks if the revenue share adds up to 100%.

type SeatAssignment

type SeatAssignment struct {
	// ID is the unique identifier for this assignment.
	ID uuid.UUID `json:"id"`

	// LicenseID references the license.
	LicenseID uuid.UUID `json:"licenseId"`

	// PrincipalID is the user assigned to this seat.
	PrincipalID uuid.UUID `json:"principalId"`

	// AssignedBy is who made this assignment.
	AssignedBy uuid.UUID `json:"assignedBy"`

	// AssignedAt is when the seat was assigned.
	AssignedAt time.Time `json:"assignedAt"`
}

SeatAssignment represents a user's assignment to a license seat.

type SeatAssignmentResponse

type SeatAssignmentResponse struct {
	ID          string    `json:"id"`
	LicenseID   string    `json:"license_id"`
	PrincipalID string    `json:"principal_id"`
	AssignedBy  string    `json:"assigned_by"`
	AssignedAt  time.Time `json:"assigned_at"`
}

SeatAssignmentResponse is the API representation of a seat assignment.

type Service

type Service interface {
	Listings() ListingService
	Licenses() LicenseService
	Subscriptions() SubscriptionService
	Checkout() CheckoutService
}

Service combines all marketplace services.

type SpiceDBSyncer

type SpiceDBSyncer struct {
	// contains filtered or unexported fields
}

SpiceDBSyncer implements AuthzSyncer using SpiceDB.

func NewSpiceDBSyncer

func NewSpiceDBSyncer(client *spicedb.Client) *SpiceDBSyncer

NewSpiceDBSyncer creates a new SpiceDB syncer for marketplace entities.

func (*SpiceDBSyncer) SyncLicense

func (s *SpiceDBSyncer) SyncLicense(ctx context.Context, license *License) error

SyncLicense syncs a license grant to SpiceDB. Creates relationships:

  • listing:listingId -> licensed_org -> organization:orgId
  • license:id -> listing -> listing:listingId
  • license:id -> organization -> organization:orgId
  • license:id -> purchased_by -> principal:purchasedBy

func (*SpiceDBSyncer) SyncLicenseRevocation

func (s *SpiceDBSyncer) SyncLicenseRevocation(ctx context.Context, license *License) error

SyncLicenseRevocation removes a license from SpiceDB. Removes relationships:

  • listing:listingId -> licensed_org -> organization:orgId

func (*SpiceDBSyncer) SyncListing

func (s *SpiceDBSyncer) SyncListing(ctx context.Context, listing *Listing) error

SyncListing syncs a listing to SpiceDB. Creates relationships:

  • listing:id -> creator_org -> creator_org:creatorOrgId
  • listing:id -> owner -> principal:ownerId

func (*SpiceDBSyncer) SyncSeatAssignment

func (s *SpiceDBSyncer) SyncSeatAssignment(ctx context.Context, assignment *SeatAssignment) error

SyncSeatAssignment syncs a seat assignment to SpiceDB. Creates relationship:

  • license:licenseId -> seat_holder -> principal:principalId

func (*SpiceDBSyncer) SyncSeatUnassignment

func (s *SpiceDBSyncer) SyncSeatUnassignment(ctx context.Context, licenseID, principalID uuid.UUID) error

SyncSeatUnassignment removes a seat assignment from SpiceDB. Removes relationship:

  • license:licenseId -> seat_holder -> principal:principalId

func (*SpiceDBSyncer) SyncSubscription

func (s *SpiceDBSyncer) SyncSubscription(ctx context.Context, sub *Subscription) error

SyncSubscription syncs a subscription to SpiceDB. Creates relationships:

  • subscription:id -> organization -> organization:orgId

type Subscription

type Subscription struct {
	// ID is the unique identifier for this subscription.
	ID uuid.UUID `json:"id"`

	// OrganizationID is the subscribing organization.
	OrganizationID uuid.UUID `json:"organizationId"`

	// PlanTier is the subscription plan name.
	PlanTier string `json:"planTier"`

	// Status is the subscription state.
	Status SubscriptionStatus `json:"status"`

	// CurrentPeriodStart is when the current billing period began.
	CurrentPeriodStart time.Time `json:"currentPeriodStart"`

	// CurrentPeriodEnd is when the current billing period ends.
	CurrentPeriodEnd time.Time `json:"currentPeriodEnd"`

	// StripeSubscriptionID is the Stripe subscription ID.
	StripeSubscriptionID string `json:"stripeSubscriptionId"`

	// StripeCustomerID is the Stripe customer ID.
	StripeCustomerID string `json:"stripeCustomerId"`

	// CancelAtPeriodEnd indicates if subscription will cancel at period end.
	CancelAtPeriodEnd bool `json:"cancelAtPeriodEnd"`

	// CreatedAt is when the subscription was created.
	CreatedAt time.Time `json:"createdAt"`

	// UpdatedAt is when the subscription was last modified.
	UpdatedAt time.Time `json:"updatedAt"`
}

Subscription represents a platform-level subscription for an organization.

func (*Subscription) DaysRemaining

func (s *Subscription) DaysRemaining() int

DaysRemaining returns the days until the current period ends.

func (*Subscription) IsActive

func (s *Subscription) IsActive() bool

IsActive returns true if the subscription is currently active.

func (*Subscription) IsInTrial

func (s *Subscription) IsInTrial() bool

IsInTrial returns true if the subscription is in trial period.

type SubscriptionService

type SubscriptionService interface {
	// Create creates a new subscription.
	Create(ctx context.Context, sub *Subscription) error

	// Get retrieves a subscription by ID.
	Get(ctx context.Context, id uuid.UUID) (*Subscription, error)

	// GetByOrg retrieves the subscription for an organization.
	GetByOrg(ctx context.Context, orgID uuid.UUID) (*Subscription, error)

	// Update updates a subscription's details.
	Update(ctx context.Context, sub *Subscription) error

	// Cancel cancels a subscription at period end.
	Cancel(ctx context.Context, id uuid.UUID) error

	// CancelImmediately cancels a subscription immediately.
	CancelImmediately(ctx context.Context, id uuid.UUID) error

	// Reactivate reactivates a canceled subscription.
	Reactivate(ctx context.Context, id uuid.UUID) error

	// ChangePlan changes the subscription plan.
	ChangePlan(ctx context.Context, id uuid.UUID, newPlan string) error

	// IsActive checks if an organization has an active subscription.
	IsActive(ctx context.Context, orgID uuid.UUID) (bool, error)

	// GetPlanTier returns the current plan tier for an organization.
	GetPlanTier(ctx context.Context, orgID uuid.UUID) (string, error)
}

SubscriptionService provides operations for platform subscriptions.

type SubscriptionStatus

type SubscriptionStatus string

SubscriptionStatus represents the state of a platform subscription.

const (
	// SubscriptionStatusActive indicates the subscription is current.
	SubscriptionStatusActive SubscriptionStatus = "active"

	// SubscriptionStatusTrialing indicates the subscription is in trial period.
	SubscriptionStatusTrialing SubscriptionStatus = "trialing"

	// SubscriptionStatusPastDue indicates payment has failed.
	SubscriptionStatusPastDue SubscriptionStatus = "past_due"

	// SubscriptionStatusCanceled indicates the subscription has been canceled.
	SubscriptionStatusCanceled SubscriptionStatus = "canceled"

	// SubscriptionStatusUnpaid indicates multiple payment failures.
	SubscriptionStatusUnpaid SubscriptionStatus = "unpaid"
)

func SubscriptionStatuses

func SubscriptionStatuses() []SubscriptionStatus

SubscriptionStatuses returns all valid subscription statuses.

type UnassignSeatInput

type UnassignSeatInput struct {
	LicenseID   string `path:"license_id" format:"uuid"`
	PrincipalID string `path:"principal_id" format:"uuid"`
}

UnassignSeatInput is the request for unassigning a seat.

type UpdateListingInput

type UpdateListingInput struct {
	ID   string `path:"id" format:"uuid"`
	Body struct {
		Title        *string        `json:"title,omitempty" maxLength:"200"`
		Description  *string        `json:"description,omitempty" maxLength:"5000"`
		PricingModel *string        `json:"pricing_model,omitempty" enum:"free,one_time,subscription,per_seat"`
		PriceCents   *int64         `json:"price_cents,omitempty" minimum:"0"`
		Currency     *string        `json:"currency,omitempty" minLength:"3" maxLength:"3"`
		ProductID    *string        `json:"product_id,omitempty" format:"uuid"`
		Metadata     map[string]any `json:"metadata,omitempty"`
	}
}

UpdateListingInput is the request for updating a listing.

type UpdateListingOutput

type UpdateListingOutput struct {
	Body *ListingResponse
}

UpdateListingOutput is the response for updating a listing.

type WebhookInput

type WebhookInput struct {
	StripeSignature string `header:"Stripe-Signature"`
	RawBody         []byte
}

WebhookInput is the request for Stripe webhooks.

Directories

Path Synopsis
Package stripe provides Stripe integration for the marketplace.
Package stripe provides Stripe integration for the marketplace.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL