cashier

package
v1.6.1 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package cashier is a multi-gateway billing plugin for Nimbus, modeled on Laravel Cashier but gateway-agnostic. One app can register several payment gateways (Stripe, Razorpay, PayU, …), pick a default, select a gateway per request, verify webhooks, and gate access with a paywall.

app.Use(cashier.NewPlugin(cashier.Config{
    FromEnv:  true,          // register gateways whose env keys are set
    Default:  "razorpay",    // default gateway
    OnWebhook: func(e cashier.WebhookEvent) error {
        switch events.Normalize(e) {
        case events.PaymentSucceeded:      // grant paywall access
        case events.SubscriptionCancelled: // revoke
        }
        return nil
    },
}))

Layout:

cashier.go   – plugin + facade      config.go   – Config
manager.go   – GatewayManager       paywall.go  – Paywall engine
routes.go    – webhook routes       contracts/  – PaymentGateway interface
gateways/    – Stripe/Razorpay/PayU  models/     – transactions, subscriptions
events/      – canonical events     views/      – checkout templates

Index

Constants

View Source
const (
	PlatformApple  = contracts.PlatformApple
	PlatformGoogle = contracts.PlatformGoogle
)
View Source
const (
	EventInitialPurchase     = "initial_purchase"
	EventRenewal             = "renewal"
	EventNonRenewingPurchase = "non_renewing_purchase"
	EventCancellation        = "cancellation"
	EventUncancellation      = "uncancellation"
	EventExpiration          = "expiration"
	EventBillingIssue        = "billing_issue"
	EventProductChange       = "product_change"
	EventPaused              = "subscription_paused"
	EventExtended            = "subscription_extended"
	EventPromotionalGrant    = "promotional_grant"
	EventPromotionalRevoke   = "promotional_revoke"
	EventTransfer            = "transfer"
)

Subscriber event types (RevenueCat parity).

View Source
const (
	PeriodTrial       = "trial"
	PeriodIntro       = "intro"
	PeriodNormal      = "normal"
	PeriodPromotional = "promotional"
	PeriodGrace       = "grace"
)

Period types carried on entitlements and events.

View Source
const (
	ReasonUnsubscribe        = "unsubscribe"
	ReasonBillingError       = "billing_error"
	ReasonDeveloperInitiated = "developer_initiated"
	ReasonPriceIncrease      = "price_increase"
	ReasonCustomerSupport    = "customer_support"
	ReasonUnknown            = "unknown"
)

Cancellation / expiration reasons.

View Source
const (
	SourcePurchase    = "purchase"
	SourcePromotional = "promotional"
)

Entitlement sources.

View Source
const DefaultGracePeriod = 72 * time.Hour

DefaultGracePeriod is how long a billing issue keeps access alive while the gateway retries the charge.

Variables

View Source
var ErrNoLifecycleStore = errors.New("cashier: lifecycle has no paywall store")

ErrNoLifecycleStore is returned when the lifecycle has no paywall to write to.

View Source
var ErrUnsupported = contracts.ErrUnsupported

ErrUnsupported is returned when a gateway lacks a requested capability.

Functions

This section is empty.

Types

type CancelParams added in v1.6.0

type CancelParams = contracts.CancelParams

Re-exported subscription and capability types so callers stay in the cashier package.

type Capabilities added in v1.6.0

type Capabilities struct {
	Subscriptions bool
	PlanChanges   bool
	Pausing       bool
	Refunds       bool
	Customers     bool
}

Capabilities describes what a gateway can do beyond a one-off charge.

type Cashier

type Cashier struct {
	Gateways *GatewayManager
	Paywall  *Paywall
	// Subscriptions mirrors gateway subscriptions locally for fast access
	// checks. Nil disables mirroring — the gateway stays the source of truth,
	// but every check then has to reach the provider.
	Subscriptions SubscriptionStore
	// IAP verifies Apple and Google in-app purchases. Nil until a verifier is
	// registered.
	IAP *IAPManager
	// Catalog maps products to entitlements and holds paywall offerings
	// (RevenueCat's Products/Entitlements/Offerings). Always non-nil.
	Catalog *Catalog
	// Lifecycle turns payment facts into entitlement changes and canonical
	// subscriber events (RevenueCat's event stream). Always non-nil.
	Lifecycle *Lifecycle
}

Cashier is the facade tying the gateway manager to the paywall. Resolve it from the container ("cashier") or hold the plugin's instance.

func (*Cashier) Cancel added in v1.6.0

func (c *Cashier) Cancel(ctx context.Context, gateway, id string, p CancelParams) (*Subscription, error)

Cancel ends a subscription. AtPeriodEnd (the humane default) keeps access until the paid period runs out.

func (*Cashier) Capabilities added in v1.6.0

func (c *Cashier) Capabilities(gateway string) (Capabilities, error)

Capabilities reports which optional capabilities a gateway implements, for a UI that hides what a provider cannot do rather than offering it and failing.

func (*Cashier) Charge

func (c *Cashier) Charge(ctx context.Context, gatewayName string, p ChargeParams) (*Charge, error)

Charge starts a payment on the named gateway (empty → the default gateway).

func (*Cashier) Customer added in v1.6.0

func (c *Cashier) Customer(ctx context.Context, gateway string, p CustomerParams) (*Customer, error)

Customer creates a gateway customer, where the gateway has the concept.

func (*Cashier) CustomerInfo added in v1.6.0

func (c *Cashier) CustomerInfo(subject string) CustomerInfo

CustomerInfo builds the aggregate for a subject from the paywall store and the subscription mirror.

func (*Cashier) HasAccess

func (c *Cashier) HasAccess(subject, plan string) bool

HasAccess is a shortcut to the paywall check.

func (*Cashier) Pause added in v1.6.0

func (c *Cashier) Pause(ctx context.Context, gateway, id string) (*Subscription, error)

Pause suspends a subscription, where the gateway allows it.

func (*Cashier) Refund added in v1.6.0

func (c *Cashier) Refund(ctx context.Context, gateway string, p RefundParams) (*Refund, error)

Refund reverses a payment, where the gateway allows it.

func (*Cashier) Resume added in v1.6.0

func (c *Cashier) Resume(ctx context.Context, gateway, id string) (*Subscription, error)

Resume restarts a paused subscription.

func (*Cashier) Subscribe added in v1.6.0

func (c *Cashier) Subscribe(ctx context.Context, gateway string, p SubscriptionParams) (*Subscription, error)

Subscribe starts a subscription on the named gateway (empty → default), and mirrors the result locally when a store is configured.

The mirror write is best-effort by design: the subscription exists at the gateway the moment it returns, and failing the caller's request because a local cache write hiccuped would be the wrong trade. A missed mirror is reconciled by the next webhook.

func (*Cashier) SubscribedTo added in v1.6.0

func (c *Cashier) SubscribedTo(subject, plan string) bool

SubscribedTo reports whether a subject has an active subscription, optionally to a specific plan (empty plan → any).

func (*Cashier) Subscription added in v1.6.0

func (c *Cashier) Subscription(ctx context.Context, gateway, id string) (*Subscription, error)

Subscription fetches a subscription from its gateway and refreshes the mirror.

func (*Cashier) Swap added in v1.6.0

func (c *Cashier) Swap(ctx context.Context, gateway, id string, p SwapParams) (*Subscription, error)

Swap changes a subscription's plan, where the gateway allows it.

func (*Cashier) VerifyPurchase added in v1.6.0

func (c *Cashier) VerifyPurchase(ctx context.Context, p ReceiptParams) (*IAPEntitlement, error)

VerifyPurchase verifies an in-app purchase through the metered manager and, when a subscription store is configured, mirrors the entitlement so an access check treats an IAP subscription exactly like a card one.

This is the method an app's receipt-validation endpoint calls: hand it the platform, product and token the client reported, and it returns only what Nimbus Cloud both cryptographically verified and metered.

type Catalog added in v1.6.0

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

Catalog is the registry of products, their entitlement mappings, and offerings. It is safe for concurrent use.

func NewCatalog added in v1.6.0

func NewCatalog() *Catalog

NewCatalog builds an empty catalogue.

func (*Catalog) CurrentOffering added in v1.6.0

func (c *Catalog) CurrentOffering() (Offering, bool)

CurrentOffering returns the offering paywalls should present now.

func (*Catalog) EntitlementsFor added in v1.6.0

func (c *Catalog) EntitlementsFor(productID string) []string

EntitlementsFor returns the entitlement ids a product unlocks. An unregistered product (or one registered without entitlements) unlocks an entitlement named after itself, which keeps plan-slug based paywalls working unchanged.

func (*Catalog) Offering added in v1.6.0

func (c *Catalog) Offering(id string) (Offering, bool)

Offering resolves an offering id.

func (*Catalog) Offerings added in v1.6.0

func (c *Catalog) Offerings() []Offering

Offerings returns every registered offering.

func (*Catalog) Product added in v1.6.0

func (c *Catalog) Product(id string) (Product, bool)

Product resolves a product id.

func (*Catalog) Products added in v1.6.0

func (c *Catalog) Products() []Product

Products returns every registered product in registration order.

func (*Catalog) ProductsUnlocking added in v1.6.0

func (c *Catalog) ProductsUnlocking(entitlementID string) []Product

ProductsUnlocking returns every product that grants an entitlement.

func (*Catalog) RegisterOffering added in v1.6.0

func (c *Catalog) RegisterOffering(o Offering)

RegisterOffering adds or replaces an offering. The first registered offering becomes current unless SetCurrentOffering has chosen one.

func (*Catalog) RegisterProduct added in v1.6.0

func (c *Catalog) RegisterProduct(p Product)

RegisterProduct adds or replaces a product.

func (*Catalog) SetCurrentOffering added in v1.6.0

func (c *Catalog) SetCurrentOffering(id string)

SetCurrentOffering picks the offering paywalls should present.

type Charge

type Charge = contracts.Charge

Re-exported contract types so callers use the cashier package without importing contracts directly.

type ChargeParams

type ChargeParams = contracts.ChargeParams

Re-exported contract types so callers use the cashier package without importing contracts directly.

type Config

type Config struct {
	// Manager is the gateway registry. Nil → a new empty one is created.
	Manager *GatewayManager
	// Default gateway name. Falls back to PAYMENTS_DEFAULT_GATEWAY, then the
	// first registered gateway.
	Default string
	// FromEnv auto-registers gateways whose credentials are present in the
	// environment (Stripe: STRIPE_KEY[/STRIPE_WEBHOOK_SECRET]; Razorpay:
	// RAZORPAY_KEY_ID/RAZORPAY_KEY_SECRET[/RAZORPAY_WEBHOOK_SECRET]; PayU:
	// PAYU_MERCHANT_KEY/PAYU_MERCHANT_SALT).
	FromEnv bool
	// Paywall backs entitlement checks (nil → in-memory store).
	Paywall *Paywall
	// WebhookPrefix mounts per-gateway webhooks at "<prefix>/<gateway>/webhook".
	// Default "/payments".
	WebhookPrefix string
	// OnWebhook runs for every verified webhook (any gateway). Use
	// events.Normalize(evt) to branch on canonical events and grant/revoke
	// paywall access.
	OnWebhook func(WebhookEvent) error
	// Subscriptions mirrors gateway subscriptions locally for fast access
	// checks. Nil → no mirror (the gateway stays authoritative).
	Subscriptions SubscriptionStore
	// IAP verifies Apple/Google in-app purchases. Nil → in-app purchases are
	// not accepted.
	IAP *IAPManager
	// Products seeds the catalogue mapping purchasable products onto the
	// entitlements they unlock (RevenueCat's Products → Entitlements).
	Products []Product
	// Offerings seeds the paywall offerings; CurrentOffering picks the one
	// paywalls present (default: the first registered).
	Offerings       []Offering
	CurrentOffering string
	// GracePeriod is how long a billing issue keeps entitlements alive while
	// the gateway retries the charge. 0 → DefaultGracePeriod (72h).
	GracePeriod time.Duration
	// OnSubscriberEvent receives every canonical lifecycle event
	// (initial_purchase, renewal, cancellation, billing_issue, …) for
	// analytics, email, or your own event bus.
	OnSubscriberEvent func(SubscriberEvent)
}

Config configures the Cashier plugin.

type Customer added in v1.6.0

type Customer = contracts.Customer

Re-exported subscription and capability types so callers stay in the cashier package.

type CustomerInfo added in v1.6.0

type CustomerInfo struct {
	Subject     string    `json:"subject"`
	RequestedAt time.Time `json:"requested_at"`

	// Entitlements is every known entitlement, keyed by id — active or not.
	Entitlements map[string]EntitlementInfo `json:"entitlements"`
	// ActiveEntitlementIDs lists the ids currently granting access, sorted.
	ActiveEntitlementIDs []string `json:"active_entitlement_ids"`
	// ActiveProductIDs lists the products behind active entitlements, sorted.
	ActiveProductIDs []string `json:"active_product_ids"`
	// ActiveSubscriptions are gateway subscriptions currently granting access,
	// from the local mirror (empty when no mirror is configured).
	ActiveSubscriptions []*Subscription `json:"active_subscriptions,omitempty"`
	// LatestExpiresAt is the furthest-out expiry across active entitlements
	// (nil when an active entitlement never expires or none are active).
	LatestExpiresAt *time.Time `json:"latest_expires_at,omitempty"`
}

CustomerInfo aggregates a subject's subscription state.

func (CustomerInfo) HasEntitlement added in v1.6.0

func (ci CustomerInfo) HasEntitlement(id string) bool

HasEntitlement reports whether the aggregate holds an active entitlement.

type CustomerParams added in v1.6.0

type CustomerParams = contracts.CustomerParams

Re-exported subscription and capability types so callers stay in the cashier package.

type Decision added in v1.6.0

type Decision = iap.Decision

Meter and its transaction/decision types, re-exported from the iap package.

type Entitlement

type Entitlement struct {
	Subject   string
	Plan      string // entitlement id
	ExpiresAt time.Time

	ProductID  string // the product whose purchase granted this ("" for promos)
	PeriodType string // trial|intro|normal|promotional|grace
	Source     string // purchase|promotional
	WillRenew  bool   // false once cancelled/paused (access persists to expiry)
}

Entitlement grants a subject (usually a user id) access to a plan until ExpiresAt. A zero ExpiresAt means it never expires.

Plan is the entitlement id being granted; the fields below carry the RevenueCat-style context around it. Stores that predate them may persist only the core triple — the lifecycle degrades gracefully.

type EntitlementInfo added in v1.6.0

type EntitlementInfo struct {
	ID         string     `json:"id"`
	Active     bool       `json:"active"`
	WillRenew  bool       `json:"will_renew"`
	PeriodType string     `json:"period_type,omitempty"` // trial|intro|normal|promotional|grace
	ProductID  string     `json:"product_id,omitempty"`
	Source     string     `json:"source,omitempty"` // purchase|promotional
	ExpiresAt  *time.Time `json:"expires_at,omitempty"`
}

EntitlementInfo is one entitlement's state inside CustomerInfo.

type EntitlementStore

type EntitlementStore interface {
	Grant(e Entitlement) error
	Revoke(subject, plan string) error
	Active(subject, plan string) (bool, error)
	List(subject string) ([]Entitlement, error)
}

EntitlementStore persists entitlements. Implement it to back the paywall with a database; MemoryEntitlementStore is the default in-memory implementation.

type Gateway

type Gateway = contracts.PaymentGateway

Re-exported contract types so callers use the cashier package without importing contracts directly.

type GatewayManager

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

GatewayManager holds the app's registered gateways and the default selection. It is safe for concurrent use — one app can serve many gateways at once and pick per request.

func NewGatewayManager

func NewGatewayManager() *GatewayManager

NewGatewayManager creates an empty gateway registry.

func (*GatewayManager) Default

func (m *GatewayManager) Default() Gateway

Default returns the default gateway, or nil when none are registered.

func (*GatewayManager) DefaultName

func (m *GatewayManager) DefaultName() string

DefaultName returns the default gateway's name.

func (*GatewayManager) Gateway

func (m *GatewayManager) Gateway(name string) (Gateway, error)

Gateway returns the gateway registered under name.

func (*GatewayManager) Names

func (m *GatewayManager) Names() []string

Names returns the registered gateway names, sorted.

func (*GatewayManager) Register

func (m *GatewayManager) Register(g Gateway) *GatewayManager

Register adds a gateway. The first one registered becomes the default until SetDefault is called. Re-registering the same name replaces it.

func (*GatewayManager) SetDefault

func (m *GatewayManager) SetDefault(name string) *GatewayManager

SetDefault selects the default gateway by name (no-op if not registered).

func (*GatewayManager) Use

func (m *GatewayManager) Use(name string) (Gateway, error)

Use resolves a gateway by name, falling back to the default when name is empty — the typical per-request selector.

type IAPEntitlement added in v1.6.0

type IAPEntitlement = contracts.IAPEntitlement

Re-exported IAP types.

type IAPManager added in v1.6.0

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

IAPManager holds the registered store verifiers and routes a request to the right one. Apple and Google are verified through entirely different APIs, so each platform registers its own verifier and this picks by platform.

Verification is a paid Nimbus Cloud feature, so a manager carries a meter and every registered verifier is wrapped by it (see iap.Metered). Constructing a manager with NewIAPManager, which has no meter, is for development and tests only; production uses NewMeteredIAPManager with a Nimbus Cloud meter.

func MeteredFromCloud added in v1.6.0

func MeteredFromCloud(apiKey string) (*IAPManager, error)

MeteredFromCloud builds a production IAP manager gated by a Nimbus Cloud meter keyed on the given API key. This is the one-liner a developer uses to turn Apple/Google verification into the paid feature: register Apple/Google verifiers on the returned manager and every entitlement is metered.

func NewIAPManager added in v1.6.0

func NewIAPManager() *IAPManager

NewIAPManager builds an unmetered manager. Verifiers registered on it are NOT gated — use it only for development and tests. Production must use NewMeteredIAPManager so transactions are metered and billed.

func NewMeteredIAPManager added in v1.6.0

func NewMeteredIAPManager(m Meter) *IAPManager

NewMeteredIAPManager builds a manager whose verifiers are all gated by the meter. This is the supported production path: without a meter, in-app purchases are not a paid feature.

func (*IAPManager) Notification added in v1.6.0

func (m *IAPManager) Notification(platform IAPPlatform, payload []byte) (*StoreNotification, error)

Notification verifies and decodes a server-to-server store notification.

func (*IAPManager) Register added in v1.6.0

func (m *IAPManager) Register(v contracts.IAPVerifier) *IAPManager

Register adds a platform verifier, wrapping it in the manager's meter when one is configured so its entitlements are gated.

func (*IAPManager) Verifier added in v1.6.0

Verifier returns the verifier for a platform.

func (*IAPManager) Verify added in v1.6.0

Verify checks a client-supplied purchase against its store and returns the entitlement the store actually vouches for.

The whole point of server-side verification is that the client is not trusted: the app hands over a token, and only the store's signed answer decides what the user owns. A caller must gate access on the returned entitlement, never on the app's own claim.

type IAPPlatform added in v1.6.0

type IAPPlatform = contracts.IAPPlatform

Re-exported IAP types.

type IAPVerifier added in v1.6.0

type IAPVerifier = contracts.IAPVerifier

Re-exported IAP types.

type Lifecycle added in v1.6.0

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

Lifecycle applies subscriber events to entitlements and emits them.

func NewLifecycle added in v1.6.0

func NewLifecycle(catalog *Catalog, paywall *Paywall, grace time.Duration, onEvent func(SubscriberEvent)) *Lifecycle

NewLifecycle builds the engine. A nil catalog gets an empty one (products then unlock entitlements named after themselves); grace <= 0 uses DefaultGracePeriod; onEvent may be nil.

func (*Lifecycle) Catalog added in v1.6.0

func (l *Lifecycle) Catalog() *Catalog

Catalog returns the product catalogue the lifecycle resolves against.

func (*Lifecycle) GrantPromotional added in v1.6.0

func (l *Lifecycle) GrantPromotional(subject, entitlementID string, until time.Time) (SubscriberEvent, error)

GrantPromotional gives an entitlement without a purchase (a comp, a beta invite, an outage credit) until `until`; zero = forever. It never renews.

func (*Lifecycle) RecordBillingIssue added in v1.6.0

func (l *Lifecycle) RecordBillingIssue(subject, productID string) (SubscriberEvent, error)

RecordBillingIssue applies a failed renewal charge: instead of revoking, it opens a grace period — each of the product's entitlements is extended to at least now+grace while the gateway retries. Returns when grace runs out.

func (*Lifecycle) RecordCancellation added in v1.6.0

func (l *Lifecycle) RecordCancellation(subject, productID, reason string) (SubscriberEvent, error)

RecordCancellation turns renewal off without touching access: the subject keeps every entitlement until it expires on its own. reason is one of the Reason* constants ("" → unsubscribe).

func (*Lifecycle) RecordExpiration added in v1.6.0

func (l *Lifecycle) RecordExpiration(subject, productID, reason string) (SubscriberEvent, error)

RecordExpiration revokes a product's entitlements now and emits expiration. Call it from the sweep that notices a lapsed period, or on a refund / immediate revocation (pass the fitting reason).

func (*Lifecycle) RecordExtension added in v1.6.0

func (l *Lifecycle) RecordExtension(subject, productID string, newExpiry time.Time) (SubscriberEvent, error)

RecordExtension pushes a subscription's expiry out (a support credit, an outage make-good) and emits subscription_extended.

func (*Lifecycle) RecordNonRenewingPurchase added in v1.6.0

func (l *Lifecycle) RecordNonRenewingPurchase(subject, productID string, expiresAt time.Time) (SubscriberEvent, error)

RecordNonRenewingPurchase applies a one-off purchase (a lifetime unlock, a consumable): entitlements are granted but nothing will renew. A zero expiresAt never lapses.

func (*Lifecycle) RecordPause added in v1.6.0

func (l *Lifecycle) RecordPause(subject, productID string) (SubscriberEvent, error)

RecordPause marks the subscription as pausing at period end (access is kept until then, like a cancellation with intent to return).

func (*Lifecycle) RecordPurchase added in v1.6.0

func (l *Lifecycle) RecordPurchase(subject, productID string, expiresAt time.Time, periodType string) (SubscriberEvent, error)

RecordPurchase applies a verified subscription payment: it grants the product's entitlements until expiresAt and emits the matching event — initial_purchase for a first-time subscriber, renewal when the subject already held this product, or product_change when they held a different paid product (whose exclusive entitlements are revoked).

periodType is one of the Period* constants; "" means PeriodNormal, and a product with trial days still in the future may pass PeriodTrial.

func (*Lifecycle) RecordUncancellation added in v1.6.0

func (l *Lifecycle) RecordUncancellation(subject, productID string) (SubscriberEvent, error)

RecordUncancellation re-enables renewal on a cancelled-but-not-expired subscription.

func (*Lifecycle) RevokePromotional added in v1.6.0

func (l *Lifecycle) RevokePromotional(subject, entitlementID string) (SubscriberEvent, error)

RevokePromotional removes a promotional entitlement.

func (*Lifecycle) Transfer added in v1.6.0

func (l *Lifecycle) Transfer(from, to string) (SubscriberEvent, error)

Transfer moves every active entitlement from one subject to another (RevenueCat's TRANSFER — a purchase restored on a device signed into a different account).

type MemoryEntitlementStore

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

MemoryEntitlementStore is a concurrency-safe in-memory EntitlementStore. Use a database-backed store in production (entitlements should survive a restart).

func NewMemoryEntitlementStore

func NewMemoryEntitlementStore() *MemoryEntitlementStore

NewMemoryEntitlementStore creates an empty in-memory store.

func (*MemoryEntitlementStore) Active

func (s *MemoryEntitlementStore) Active(subject, plan string) (bool, error)

func (*MemoryEntitlementStore) Grant

func (*MemoryEntitlementStore) List

func (s *MemoryEntitlementStore) List(subject string) ([]Entitlement, error)

func (*MemoryEntitlementStore) Revoke

func (s *MemoryEntitlementStore) Revoke(subject, plan string) error

type MemorySubscriptionStore added in v1.6.0

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

MemorySubscriptionStore is a process-local SubscriptionStore. It is the default and is intended for tests and single-process apps; a real deployment backs the mirror with the cashier_subscriptions table.

func NewMemorySubscriptionStore added in v1.6.0

func NewMemorySubscriptionStore() *MemorySubscriptionStore

NewMemorySubscriptionStore builds an empty in-memory store.

func (*MemorySubscriptionStore) ActiveForSubject added in v1.6.0

func (m *MemorySubscriptionStore) ActiveForSubject(subject string) []*contracts.Subscription

ActiveForSubject returns only the subscriptions that currently grant access.

func (*MemorySubscriptionStore) ForSubject added in v1.6.0

func (m *MemorySubscriptionStore) ForSubject(subject string) []*contracts.Subscription

ForSubject returns every mirrored subscription for a subject.

func (*MemorySubscriptionStore) Get added in v1.6.0

func (m *MemorySubscriptionStore) Get(gateway, id string) (*contracts.Subscription, bool)

Get returns one mirrored subscription.

func (*MemorySubscriptionStore) Upsert added in v1.6.0

Upsert inserts or replaces the mirror of a subscription.

type Meter added in v1.6.0

type Meter = iap.Meter

Meter and its transaction/decision types, re-exported from the iap package.

type MeteredTransaction added in v1.6.0

type MeteredTransaction = iap.MeteredTransaction

Meter and its transaction/decision types, re-exported from the iap package.

type Offering added in v1.6.0

type Offering struct {
	ID       string            `json:"id"`
	Packages []Package         `json:"packages"`
	Metadata map[string]string `json:"metadata,omitempty"`
}

Offering is a named group of packages a paywall presents.

type Package added in v1.6.0

type Package struct {
	ID        string `json:"id"` // e.g. "monthly", "annual"
	ProductID string `json:"product_id"`
}

Package is one product slot inside an offering.

type PaymentProof

type PaymentProof = contracts.PaymentProof

Re-exported contract types so callers use the cashier package without importing contracts directly.

type Paywall

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

Paywall gates access to plans/features based on entitlements. Grant on a successful payment (typically in a webhook handler); gate routes with RequirePlan.

func NewPaywall

func NewPaywall(store EntitlementStore) *Paywall

NewPaywall builds a paywall over a store (nil → in-memory).

func (*Paywall) Grant

func (p *Paywall) Grant(subject, plan string, expires time.Time) error

Grant gives subject access to plan until expires (zero time = forever).

func (*Paywall) HasAccess

func (p *Paywall) HasAccess(subject, plan string) bool

HasAccess reports whether subject currently has active access to plan.

func (*Paywall) RequirePlan

func (p *Paywall) RequirePlan(plan string, subjectFn func(*nhttp.Context) string) router.Middleware

RequirePlan returns middleware that blocks a route unless the current subject has active access to plan. subjectFn extracts the subject (e.g. the user id) from the request; return "" for an anonymous/unknown user. Blocked requests receive HTTP 402 Payment Required.

func (*Paywall) Revoke

func (p *Paywall) Revoke(subject, plan string) error

Revoke removes a subject's access to a plan.

func (*Paywall) Store

func (p *Paywall) Store() EntitlementStore

Store returns the underlying store.

type Plugin

type Plugin struct {
	nimbus.BasePlugin
	Cashier *Cashier
	// contains filtered or unexported fields
}

Plugin wires Cashier into Nimbus.

func NewPlugin

func NewPlugin(cfg Config) *Plugin

NewPlugin builds the Cashier plugin from config.

func (*Plugin) Boot

func (p *Plugin) Boot(app *nimbus.App) error

func (*Plugin) DefaultConfig

func (p *Plugin) DefaultConfig() map[string]any

func (*Plugin) Migrations

func (p *Plugin) Migrations() []database.Migration

Migrations creates the cashier tables (transactions, subscriptions).

func (*Plugin) Register

func (p *Plugin) Register(app *nimbus.App) error

func (*Plugin) RegisterRoutes

func (p *Plugin) RegisterRoutes(r *router.Router)

RegisterRoutes mounts a signature-verified webhook endpoint per gateway at "<WebhookPrefix>/<gateway>/webhook" (e.g. /payments/razorpay/webhook).

type Product added in v1.6.0

type Product struct {
	ID           string   `json:"id"`   // plan/price identifier ("pro", "price_123")
	Name         string   `json:"name"` // display name
	Amount       int64    `json:"amount"`
	Currency     string   `json:"currency"`
	PeriodMonths int      `json:"period_months"` // billing period; 0 = non-renewing / lifetime
	TrialDays    int      `json:"trial_days"`
	Entitlements []string `json:"entitlements"` // entitlement ids this product unlocks
}

Product is one purchasable SKU and the entitlements it unlocks.

func (Product) Paid added in v1.6.0

func (p Product) Paid() bool

Paid reports whether the product requires a payment.

type ReceiptParams added in v1.6.0

type ReceiptParams = contracts.ReceiptParams

Re-exported IAP types.

type Refund added in v1.6.0

type Refund = contracts.Refund

Re-exported subscription and capability types so callers stay in the cashier package.

type RefundParams added in v1.6.0

type RefundParams = contracts.RefundParams

Re-exported subscription and capability types so callers stay in the cashier package.

type StoreNotification added in v1.6.0

type StoreNotification = contracts.StoreNotification

Re-exported IAP types.

type SubscriberEvent added in v1.6.0

type SubscriberEvent struct {
	Type           string     `json:"type"`
	Subject        string     `json:"subject"`
	ProductID      string     `json:"product_id,omitempty"`
	OldProductID   string     `json:"old_product_id,omitempty"` // product_change
	FromSubject    string     `json:"from_subject,omitempty"`   // transfer
	EntitlementIDs []string   `json:"entitlement_ids,omitempty"`
	PeriodType     string     `json:"period_type,omitempty"`
	Reason         string     `json:"reason,omitempty"`
	ExpiresAt      *time.Time `json:"expires_at,omitempty"`
	At             time.Time  `json:"at"`
}

SubscriberEvent is one canonical moment in a subscriber's life.

type Subscription added in v1.6.0

type Subscription = contracts.Subscription

Re-exported subscription and capability types so callers stay in the cashier package.

type SubscriptionParams added in v1.6.0

type SubscriptionParams = contracts.SubscriptionParams

Re-exported subscription and capability types so callers stay in the cashier package.

type SubscriptionStatus added in v1.6.0

type SubscriptionStatus = contracts.SubscriptionStatus

Re-exported subscription and capability types so callers stay in the cashier package.

type SubscriptionStore added in v1.6.0

type SubscriptionStore interface {
	Upsert(sub *contracts.Subscription) error
	Get(gateway, id string) (*contracts.Subscription, bool)
	ForSubject(subject string) []*contracts.Subscription
	ActiveForSubject(subject string) []*contracts.Subscription
}

SubscriptionStore persists the local mirror of gateway subscriptions.

Upsert is keyed on (gateway, gateway id): a webhook, a verification and a manual refresh all describe the same subscription, and each must update the one row rather than insert a duplicate. Implement it over your database; the in-memory store is the default and is what the tests run against.

type SwapParams added in v1.6.0

type SwapParams = contracts.SwapParams

Re-exported subscription and capability types so callers stay in the cashier package.

type WebhookEvent

type WebhookEvent = contracts.WebhookEvent

Re-exported contract types so callers use the cashier package without importing contracts directly.

Directories

Path Synopsis
Package contracts defines the payment-gateway contract and the shared value types every gateway and the manager operate on.
Package contracts defines the payment-gateway contract and the shared value types every gateway and the manager operate on.
Package events defines canonical, gateway-agnostic payment events and maps each gateway's raw webhook types onto them — so paywall logic (grant/revoke) is written once regardless of which gateway delivered the event.
Package events defines canonical, gateway-agnostic payment events and maps each gateway's raw webhook types onto them — so paywall logic (grant/revoke) is written once regardless of which gateway delivered the event.
Package gateways holds the concrete payment gateways (Stripe, Razorpay, PayU, …).
Package gateways holds the concrete payment gateways (Stripe, Razorpay, PayU, …).
Package iap holds the Apple and Google in-app-purchase verifiers.
Package iap holds the Apple and Google in-app-purchase verifiers.
Package models holds the persisted Cashier tables (transactions, subscriptions) and their migrations.
Package models holds the persisted Cashier tables (transactions, subscriptions) and their migrations.

Jump to

Keyboard shortcuts

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