Documentation
¶
Overview ¶
Package marketplace provides a unified framework for two-sided marketplace applications including listings, licenses, and subscriptions.
Index ¶
- Constants
- Variables
- func ApproleOptions(src organization.EntitlementSource) []approle.Option
- func EntitlementSource(subs SubscriptionService, licenses LicenseService, listings ListingService, ...) organization.EntitlementSource
- func MergeSchema(appSchema string) string
- func PlanTiers() []string
- func RequestPrincipalCaller(ctx context.Context) (uuid.UUID, error)
- func WarnIfNoEntitlements(ctx context.Context, logger *slog.Logger, listings ListingService, ...) (bool, error)
- type API
- type APIConfig
- type APIOption
- type ArchiveListingInput
- type ArchiveListingOutput
- type AssignSeatInput
- type AssignSeatOutput
- type AuthzSyncer
- type CallerResolver
- type CheckoutRequest
- type CheckoutService
- type CheckoutSession
- type CreateCheckoutInput
- type CreateCheckoutOutput
- type CreateListingInput
- type CreateListingOutput
- type DeleteListingInput
- type EntLicenseService
- func (s *EntLicenseService) AssignSeat(ctx context.Context, assignment *SeatAssignment) error
- func (s *EntLicenseService) Check(ctx context.Context, listingID, orgID uuid.UUID) (bool, error)
- func (s *EntLicenseService) CheckPrincipal(ctx context.Context, listingID, principalID uuid.UUID) (bool, error)
- func (s *EntLicenseService) Get(ctx context.Context, id uuid.UUID) (*License, error)
- func (s *EntLicenseService) GetByListingAndOrg(ctx context.Context, listingID, orgID uuid.UUID) (*License, error)
- func (s *EntLicenseService) GetByStripeSubscriptionID(ctx context.Context, subscriptionID string) (*License, error)
- func (s *EntLicenseService) Grant(ctx context.Context, l *License) error
- func (s *EntLicenseService) List(ctx context.Context, orgID uuid.UUID) ([]*License, error)
- func (s *EntLicenseService) ListByListing(ctx context.Context, listingID uuid.UUID) ([]*License, error)
- func (s *EntLicenseService) ListSeatAssignments(ctx context.Context, licenseID uuid.UUID) ([]*SeatAssignment, error)
- func (s *EntLicenseService) Revoke(ctx context.Context, id uuid.UUID) error
- func (s *EntLicenseService) UnassignSeat(ctx context.Context, licenseID, principalID uuid.UUID) error
- func (s *EntLicenseService) Update(ctx context.Context, l *License) error
- type EntListingService
- func (s *EntListingService) Archive(ctx context.Context, id uuid.UUID) error
- func (s *EntListingService) Create(ctx context.Context, l *Listing) error
- func (s *EntListingService) Delete(ctx context.Context, id uuid.UUID) error
- func (s *EntListingService) Get(ctx context.Context, id uuid.UUID) (*Listing, error)
- func (s *EntListingService) GetByProduct(ctx context.Context, productType string, productID uuid.UUID) (*Listing, error)
- func (s *EntListingService) List(ctx context.Context, opts ListListingsOptions) ([]*Listing, error)
- func (s *EntListingService) ListByCreator(ctx context.Context, creatorOrgID uuid.UUID) ([]*Listing, error)
- func (s *EntListingService) Publish(ctx context.Context, id uuid.UUID) error
- func (s *EntListingService) Update(ctx context.Context, l *Listing) error
- type EntService
- type EntSubscriptionService
- func (s *EntSubscriptionService) Cancel(ctx context.Context, id uuid.UUID) error
- func (s *EntSubscriptionService) CancelImmediately(ctx context.Context, id uuid.UUID) error
- func (s *EntSubscriptionService) ChangePlan(ctx context.Context, id uuid.UUID, newPlan string) error
- func (s *EntSubscriptionService) Create(ctx context.Context, sub *Subscription) error
- func (s *EntSubscriptionService) Get(ctx context.Context, id uuid.UUID) (*Subscription, error)
- func (s *EntSubscriptionService) GetByOrg(ctx context.Context, orgID uuid.UUID) (*Subscription, error)
- func (s *EntSubscriptionService) GetPlanTier(ctx context.Context, orgID uuid.UUID) (string, error)
- func (s *EntSubscriptionService) IsActive(ctx context.Context, orgID uuid.UUID) (bool, error)
- func (s *EntSubscriptionService) ListByOrg(ctx context.Context, orgID uuid.UUID) ([]*Subscription, error)
- func (s *EntSubscriptionService) Reactivate(ctx context.Context, id uuid.UUID) error
- func (s *EntSubscriptionService) Update(ctx context.Context, sub *Subscription) error
- type EntitlementOption
- type GetLicenseInput
- type GetLicenseOutput
- type GetListingInput
- type GetListingOutput
- type License
- type LicenseResponse
- type LicenseService
- type LicenseType
- type ListLicensesInput
- type ListLicensesOutput
- type ListListingsInput
- type ListListingsOptions
- type ListListingsOutput
- type ListSeatsInput
- type ListSeatsOutput
- type Listing
- type ListingResponse
- type ListingService
- type ListingStatus
- type PricingModel
- type PublishListingInput
- type PublishListingOutput
- type PurchaseAuthorizer
- type PurchaseAuthorizerFunc
- type PurchaserChecker
- type RevenueShare
- type SeatAssignment
- type SeatAssignmentResponse
- type Service
- type SpiceDBSyncer
- func (s *SpiceDBSyncer) SyncLicense(ctx context.Context, license *License) error
- func (s *SpiceDBSyncer) SyncLicenseRevocation(ctx context.Context, license *License) error
- func (s *SpiceDBSyncer) SyncListing(ctx context.Context, listing *Listing) error
- func (s *SpiceDBSyncer) SyncSeatAssignment(ctx context.Context, assignment *SeatAssignment) error
- func (s *SpiceDBSyncer) SyncSeatUnassignment(ctx context.Context, licenseID, principalID uuid.UUID) error
- func (s *SpiceDBSyncer) SyncSubscription(ctx context.Context, sub *Subscription) error
- type Subscription
- type SubscriptionLister
- type SubscriptionService
- type SubscriptionStatus
- type UnassignSeatInput
- type UpdateListingInput
- type UpdateListingOutput
- type WebhookInput
Constants ¶
const ( ResourceTypeListing = "listing" ResourceTypeLicense = "license" ResourceTypeSubscription = "subscription" ResourceTypeOrganization = "organization" ResourceTypeCreatorOrg = "creator_org" ResourceTypePrincipal = "principal" )
SpiceDB resource types for marketplace entities.
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.
const ( PlanTierFree = "free" PlanTierStarter = "starter" PlanTierPro = "pro" PlanTierEnterprise = "enterprise" )
PlanTier constants for common subscription tiers.
Variables ¶
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 = 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") // ErrManagedPurchaser is returned when a managed principal (a human // without an email, ADR-005) tries to purchase; billing is a guardian's // action. ErrManagedPurchaser = errors.New("managed principals cannot purchase") // 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.
var DefaultPurchaseRoles = []string{"owner", "admin"}
DefaultPurchaseRoles are the organization roles that may buy for it: the roles that manage the organization.
var ErrNoCaller = errors.New("marketplace: no authenticated caller")
ErrNoCaller is returned when a request carries no authenticated principal that the marketplace can act for.
var MarketplaceSchema string
MarketplaceSchema is the marketplace's SpiceDB schema fragment. It does not stand alone: it attaches to the "principal" and "organization" definitions of authz.BaseSpiceDBSchema through the marketplace_org facet, as the vocabulary rules require. Use MergeSchema to compose it.
Functions ¶
func ApproleOptions ¶ added in v0.14.0
func ApproleOptions(src organization.EntitlementSource) []approle.Option
ApproleOptions returns the approle.Service options that make src the source of which applications an organization may assign roles in, and how many seats each has. An application is entitled when any entitlement names it. Its seat limit is the sum of the seats of its seat-based licenses, and none when any entitlement for it is not seat-limited (a subscription, an unlimited license, a default).
func EntitlementSource ¶ added in v0.14.0
func EntitlementSource(subs SubscriptionService, licenses LicenseService, listings ListingService, opts ...EntitlementOption) organization.EntitlementSource
EntitlementSource returns an organization.EntitlementSource that derives an organization's entitlements from its subscriptions, its valid licenses and the deployment's default entitlements.
A subscription or license grants an application when its listing names one (Listing.Application). The organization-wide platform subscription has no listing and grants none; it is reported for its tier. A seat-based license carries its seat count. Any of subs, licenses and listings may be nil to leave that source out; without listings no subscription or license grants an application. subs that also implements SubscriptionLister contributes every subscription of the organization, otherwise only its platform subscription.
func MergeSchema ¶
MergeSchema composes the SystemForge base schema, the marketplace schema and an application-specific schema. The app schema should define app-specific resource types that reference marketplace types (listing, license, etc.).
func RequestPrincipalCaller ¶ added in v0.14.0
RequestPrincipalCaller resolves the caller from requestprincipal, as set by requestprincipal.Middleware. Principals whose ID is not a SystemForge principal ID, such as issuer-qualified subjects of credentials issued elsewhere, cannot act in the marketplace.
func WarnIfNoEntitlements ¶ added in v0.14.0
func WarnIfNoEntitlements(ctx context.Context, logger *slog.Logger, listings ListingService, registered []string, opts ...EntitlementOption) (bool, error)
WarnIfNoEntitlements logs a warning when the deployment registers applications but nothing can entitle an organization to them: no default entitlement and no listing that names an application. Every organization is then refused every application role (ADR-007). Call it at startup with the registered application identifiers; it reports whether it warned.
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API handles marketplace HTTP endpoints.
type APIConfig ¶
type APIConfig struct {
// BasePath is the API base path (e.g., "/api/v1").
BasePath string
}
APIConfig holds API configuration.
type APIOption ¶ added in v0.14.0
type APIOption func(*API)
APIOption configures an API.
func WithAuthorizer ¶ added in v0.14.0
func WithAuthorizer(a authz.Authorizer) APIOption
WithAuthorizer sets the authorizer the API checks organization permissions with (authz.PermOrgView, PermOrgEdit, PermOrgManage on the listing's creator organization or the license's organization). Until one is configured, every organization-scoped operation is refused; only published listings can be read.
func WithCallerResolver ¶ added in v0.14.0
func WithCallerResolver(r CallerResolver) APIOption
WithCallerResolver sets how the API identifies the calling principal. Defaults to RequestPrincipalCaller.
func WithPurchaseAuthorizer ¶ added in v0.14.0
func WithPurchaseAuthorizer(p PurchaseAuthorizer) APIOption
WithPurchaseAuthorizer sets who may buy for an organization, for example OrgRolePurchaseAuthorizer(orgAuthorizer). Without one, buying requires authz.PermOrgManage through the authorizer set by WithAuthorizer; with neither, checkout is refused.
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 `` /* 157-byte string literal not displayed */
}
}
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 CallerResolver ¶ added in v0.14.0
CallerResolver returns the principal making a request. It returns ErrNoCaller when the request is unauthenticated.
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 is deprecated: the purchaser is the authenticated
// caller. When present it must name the caller.
PurchaserID string `` /* 159-byte string literal not displayed */
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 `` /* 151-byte string literal not displayed */
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 ¶
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) 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) GetByStripeSubscriptionID ¶ added in v0.14.0
func (s *EntLicenseService) GetByStripeSubscriptionID(ctx context.Context, subscriptionID string) (*License, error)
GetByStripeSubscriptionID retrieves the license a Stripe subscription pays for.
func (*EntLicenseService) Grant ¶
func (s *EntLicenseService) Grant(ctx context.Context, l *License) error
Grant creates a new license 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) UnassignSeat ¶
func (s *EntLicenseService) UnassignSeat(ctx context.Context, licenseID, principalID uuid.UUID) error
UnassignSeat removes a user from a license seat.
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) Create ¶
func (s *EntListingService) Create(ctx context.Context, l *Listing) error
Create creates a new listing in draft status.
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 ¶
func (s *EntListingService) List(ctx context.Context, opts ListListingsOptions) ([]*Listing, error)
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.
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) CancelImmediately ¶
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 ¶
func (s *EntSubscriptionService) Create(ctx context.Context, sub *Subscription) error
Create creates a new subscription.
func (*EntSubscriptionService) Get ¶
func (s *EntSubscriptionService) Get(ctx context.Context, id uuid.UUID) (*Subscription, error)
Get retrieves a subscription by ID.
func (*EntSubscriptionService) GetByOrg ¶
func (s *EntSubscriptionService) GetByOrg(ctx context.Context, orgID uuid.UUID) (*Subscription, error)
GetByOrg retrieves the organization-wide platform subscription of an organization, the one with no listing. Use ListByOrg for the per-listing subscriptions.
func (*EntSubscriptionService) GetPlanTier ¶
GetPlanTier returns the current plan tier for an organization.
func (*EntSubscriptionService) IsActive ¶
IsActive checks if an organization has an active subscription.
func (*EntSubscriptionService) ListByOrg ¶ added in v0.14.0
func (s *EntSubscriptionService) ListByOrg(ctx context.Context, orgID uuid.UUID) ([]*Subscription, error)
ListByOrg lists every subscription of an organization: the platform subscription and those for individual listings.
func (*EntSubscriptionService) Reactivate ¶
Reactivate reactivates a canceled subscription.
func (*EntSubscriptionService) Update ¶
func (s *EntSubscriptionService) Update(ctx context.Context, sub *Subscription) error
Update updates a subscription's details.
type EntitlementOption ¶ added in v0.14.0
type EntitlementOption func(*entitlementConfig)
EntitlementOption configures EntitlementSource.
func WithDefaultEntitlements ¶ added in v0.14.0
func WithDefaultEntitlements(applications ...string) EntitlementOption
WithDefaultEntitlements declares applications every organization is entitled to, with unlimited seats: applications that are free for everyone, or a deployment that does not sell access through the marketplace. It is the deployment's marketplace.default_entitlements.
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 ¶
HasAvailableSeats returns true if seats are available for assignment.
func (*License) SeatsRemaining ¶
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)
// GetByStripeSubscriptionID retrieves the license a Stripe
// subscription pays for, or ErrLicenseNotFound.
GetByStripeSubscriptionID(ctx context.Context, subscriptionID string) (*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" )
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 {
// Huma does not support pointer parameters; an empty value means
// the filter is not applied.
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"`
// Application is the registered application identifier (an
// authz.AppVocabulary application) that an active subscription or valid
// license on this listing entitles its organization to. Empty when the
// listing does not grant an application.
Application string `json:"application,omitempty"`
// 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) IsPublished ¶
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"`
Application string `json:"application,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 PurchaseAuthorizer ¶ added in v0.14.0
type PurchaseAuthorizer interface {
CanPurchase(ctx context.Context, principalID, orgID uuid.UUID) (bool, error)
}
PurchaseAuthorizer decides whether a principal may buy on behalf of an organization.
func OrgRolePurchaseAuthorizer ¶ added in v0.14.0
func OrgRolePurchaseAuthorizer(orgs authz.OrgAuthorizer, roles ...string) PurchaseAuthorizer
OrgRolePurchaseAuthorizer allows purchases by principals holding one of roles in the organization, DefaultPurchaseRoles when none are given. It uses the organization role, which both authorization providers report the same way.
type PurchaseAuthorizerFunc ¶ added in v0.14.0
PurchaseAuthorizerFunc adapts a function to PurchaseAuthorizer.
func (PurchaseAuthorizerFunc) CanPurchase ¶ added in v0.14.0
func (f PurchaseAuthorizerFunc) CanPurchase(ctx context.Context, principalID, orgID uuid.UUID) (bool, error)
CanPurchase implements PurchaseAuthorizer.
type PurchaserChecker ¶ added in v0.14.0
type PurchaserChecker interface {
IsManaged(ctx context.Context, principalID uuid.UUID) (bool, error)
}
PurchaserChecker reports whether a principal is managed: a human without an email whose managers control the account. Managed principals cannot purchase. identity/managedprincipal.Service implements it.
type RevenueShare ¶
type RevenueShare struct {
CreatorPercent int `json:"creatorPercent"`
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"`
// ListingID is the listing this subscription is for. It is nil for the
// organization-wide platform subscription, of which an organization
// has at most one; it may hold one subscription per listing.
ListingID *uuid.UUID `json:"listingId,omitempty"`
// 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 SubscriptionLister ¶ added in v0.14.0
type SubscriptionLister interface {
ListByOrg(ctx context.Context, orgID uuid.UUID) ([]*Subscription, error)
}
SubscriptionLister is implemented by a SubscriptionService that can list every subscription of an organization, the platform subscription and those for individual listings. It is a separate interface so existing SubscriptionService implementers keep compiling; without it, only the platform subscription from GetByOrg is considered.
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 ¶
WebhookInput is the request for Stripe webhooks.