Documentation
¶
Overview ¶
Package postgres provides a PostgreSQL Store implementation for Ledger using Grove ORM.
Index ¶
- Variables
- type Store
- func (s *Store) AdvanceSubscriptionPeriod(ctx context.Context, subID id.SubscriptionID, from, start, end, at time.Time) (bool, error)
- func (s *Store) Aggregate(ctx context.Context, tenantID, appID, featureKey string, period plan.Period) (int64, error)
- func (s *Store) AggregateMulti(ctx context.Context, tenantID, appID string, featureKeys []string, ...) (map[string]int64, error)
- func (s *Store) ApplyCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
- func (s *Store) ArchiveFeature(ctx context.Context, featureID id.FeatureID) error
- func (s *Store) ArchivePlan(ctx context.Context, planID id.PlanID) error
- func (s *Store) CancelSubscription(ctx context.Context, subID id.SubscriptionID, immediately bool) (time.Time, error)
- func (s *Store) ChangeSubscriptionPlan(ctx context.Context, subID id.SubscriptionID, planID id.PlanID, ...) (bool, error)
- func (s *Store) Close() error
- func (s *Store) CreateCoupon(ctx context.Context, c *coupon.Coupon) error
- func (s *Store) CreateFeature(ctx context.Context, f *feature.Feature) error
- func (s *Store) CreateInvoice(ctx context.Context, inv *invoice.Invoice) error
- func (s *Store) CreatePlan(ctx context.Context, p *plan.Plan) error
- func (s *Store) CreateSubscription(ctx context.Context, sub *subscription.Subscription) error
- func (s *Store) DB() *grove.DB
- func (s *Store) DeleteCoupon(ctx context.Context, couponID id.CouponID) error
- func (s *Store) DeleteFeature(ctx context.Context, featureID id.FeatureID) error
- func (s *Store) DeletePlan(ctx context.Context, planID id.PlanID) error
- func (s *Store) EnactSubscriptionCancel(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
- func (s *Store) EndSubscriptionTrial(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
- func (s *Store) GetActiveSubscription(ctx context.Context, tenantID, appID string) (*subscription.Subscription, error)
- func (s *Store) GetCached(ctx context.Context, tenantID, appID, featureKey string) (*entitlement.Result, error)
- func (s *Store) GetCoupon(ctx context.Context, code, appID string) (*coupon.Coupon, error)
- func (s *Store) GetCouponByID(ctx context.Context, couponID id.CouponID) (*coupon.Coupon, error)
- func (s *Store) GetFeature(ctx context.Context, featureID id.FeatureID) (*feature.Feature, error)
- func (s *Store) GetFeatureByKey(ctx context.Context, key, appID string) (*feature.Feature, error)
- func (s *Store) GetInvoice(ctx context.Context, invID id.InvoiceID) (*invoice.Invoice, error)
- func (s *Store) GetInvoiceByPeriod(ctx context.Context, tenantID, appID string, periodStart, periodEnd time.Time) (*invoice.Invoice, error)
- func (s *Store) GetPlan(ctx context.Context, planID id.PlanID) (*plan.Plan, error)
- func (s *Store) GetPlanBySlug(ctx context.Context, slug, appID string) (*plan.Plan, error)
- func (s *Store) GetSubscription(ctx context.Context, subID id.SubscriptionID) (*subscription.Subscription, error)
- func (s *Store) IncrementCouponRedemptions(ctx context.Context, couponID id.CouponID) error
- func (s *Store) IngestBatch(ctx context.Context, events []*meter.UsageEvent) error
- func (s *Store) Invalidate(ctx context.Context, tenantID, appID string) error
- func (s *Store) InvalidateFeature(ctx context.Context, tenantID, appID, featureKey string) error
- func (s *Store) ListAppliedCoupons(ctx context.Context, subID id.SubscriptionID) ([]*coupon.Coupon, error)
- func (s *Store) ListCoupons(ctx context.Context, appID string, opts coupon.ListOpts) ([]*coupon.Coupon, error)
- func (s *Store) ListDueSubscriptions(ctx context.Context, opts subscription.DueOpts) ([]*subscription.Subscription, error)
- func (s *Store) ListFeatures(ctx context.Context, appID string, opts feature.ListOpts) ([]*feature.Feature, error)
- func (s *Store) ListGlobalFeatures(ctx context.Context, opts feature.ListOpts) ([]*feature.Feature, error)
- func (s *Store) ListInvoices(ctx context.Context, tenantID, appID string, opts invoice.ListOpts) ([]*invoice.Invoice, error)
- func (s *Store) ListOverdueInvoices(ctx context.Context, opts invoice.OverdueOpts) ([]*invoice.Invoice, error)
- func (s *Store) ListPendingInvoices(ctx context.Context, appID string) ([]*invoice.Invoice, error)
- func (s *Store) ListPlans(ctx context.Context, appID string, opts plan.ListOpts) ([]*plan.Plan, error)
- func (s *Store) ListSubscriptions(ctx context.Context, tenantID, appID string, opts subscription.ListOpts) ([]*subscription.Subscription, error)
- func (s *Store) MarkInvoicePaid(ctx context.Context, invID id.InvoiceID, paidAt time.Time, paymentRef string) error
- func (s *Store) MarkInvoicePastDue(ctx context.Context, invID id.InvoiceID, at time.Time) (bool, error)
- func (s *Store) MarkInvoiceVoided(ctx context.Context, invID id.InvoiceID, reason string) error
- func (s *Store) Migrate(ctx context.Context) error
- func (s *Store) PauseSubscription(ctx context.Context, subID id.SubscriptionID, at time.Time) (bool, error)
- func (s *Store) Ping(ctx context.Context) error
- func (s *Store) PurgeUsage(ctx context.Context, before time.Time) (int64, error)
- func (s *Store) QueryUsage(ctx context.Context, tenantID, appID string, opts meter.QueryOpts) ([]*meter.UsageEvent, error)
- func (s *Store) RedeemCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
- func (s *Store) ResumeSubscription(ctx context.Context, subID id.SubscriptionID, r subscription.Resume) (bool, error)
- func (s *Store) SetCached(ctx context.Context, tenantID, appID, featureKey string, ...) error
- func (s *Store) SetInvoiceProvider(ctx context.Context, invID id.InvoiceID, providerID, providerName string) error
- func (s *Store) SetSubscriptionProvider(ctx context.Context, subID id.SubscriptionID, providerID, providerName string) error
- func (s *Store) UpdateCoupon(ctx context.Context, c *coupon.Coupon) error
- func (s *Store) UpdateFeature(ctx context.Context, f *feature.Feature) error
- func (s *Store) UpdateInvoice(ctx context.Context, inv *invoice.Invoice) error
- func (s *Store) UpdatePlan(ctx context.Context, p *plan.Plan) error
- func (s *Store) UpdateSubscription(ctx context.Context, sub *subscription.Subscription) error
Constants ¶
This section is empty.
Variables ¶
var Migrations = migrate.NewGroup("ledger")
Migrations is the grove migration group for the Ledger store.
Functions ¶
This section is empty.
Types ¶
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store implements store.Store using PostgreSQL via Grove ORM.
func (*Store) AdvanceSubscriptionPeriod ¶
func (*Store) AggregateMulti ¶
func (*Store) ApplyCoupon ¶
func (s *Store) ApplyCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
ApplyCoupon is a low-level operation kept for callers and tests that need it separately. Engine code redeems through RedeemCoupon, which records the application and increments the redemption count as one unit.
func (*Store) ArchiveFeature ¶
func (*Store) ArchivePlan ¶
func (*Store) CancelSubscription ¶
func (*Store) ChangeSubscriptionPlan ¶
func (*Store) CreateCoupon ¶
func (*Store) CreateFeature ¶
func (*Store) CreateInvoice ¶
func (*Store) CreateSubscription ¶
func (s *Store) CreateSubscription(ctx context.Context, sub *subscription.Subscription) error
func (*Store) DeleteCoupon ¶
func (*Store) DeleteFeature ¶
func (*Store) EnactSubscriptionCancel ¶
func (*Store) EndSubscriptionTrial ¶
func (*Store) GetActiveSubscription ¶
func (s *Store) GetActiveSubscription(ctx context.Context, tenantID, appID string) (*subscription.Subscription, error)
func (*Store) GetCouponByID ¶
func (*Store) GetFeature ¶
func (*Store) GetFeatureByKey ¶
func (*Store) GetInvoice ¶
func (*Store) GetInvoiceByPeriod ¶
func (*Store) GetPlanBySlug ¶
func (*Store) GetSubscription ¶
func (s *Store) GetSubscription(ctx context.Context, subID id.SubscriptionID) (*subscription.Subscription, error)
func (*Store) IncrementCouponRedemptions ¶
IncrementCouponRedemptions is a low-level operation kept for callers and tests that need it separately. Engine code redeems through RedeemCoupon, which increments the count and records the application as one unit.
func (*Store) IngestBatch ¶
func (*Store) Invalidate ¶
func (*Store) InvalidateFeature ¶
func (*Store) ListAppliedCoupons ¶
func (*Store) ListCoupons ¶
func (*Store) ListDueSubscriptions ¶
func (s *Store) ListDueSubscriptions(ctx context.Context, opts subscription.DueOpts) ([]*subscription.Subscription, error)
func (*Store) ListFeatures ¶
func (*Store) ListGlobalFeatures ¶
func (*Store) ListInvoices ¶
func (*Store) ListOverdueInvoices ¶
func (*Store) ListPendingInvoices ¶
func (*Store) ListSubscriptions ¶
func (s *Store) ListSubscriptions(ctx context.Context, tenantID, appID string, opts subscription.ListOpts) ([]*subscription.Subscription, error)
func (*Store) MarkInvoicePaid ¶
func (*Store) MarkInvoicePastDue ¶
func (*Store) MarkInvoiceVoided ¶
func (*Store) Migrate ¶
Migrate creates the required tables and indexes using the grove orchestrator.
func (*Store) PauseSubscription ¶
func (*Store) PurgeUsage ¶
func (*Store) QueryUsage ¶
func (*Store) RedeemCoupon ¶
func (s *Store) RedeemCoupon(ctx context.Context, subID id.SubscriptionID, couponID id.CouponID) error
RedeemCoupon records a coupon's application to a subscription and increments its redemption count in one transaction, committing only when both writes land. The cap (MaxRedemptions) is enforced by a conditional UPDATE - `times_redeemed = times_redeemed + 1 WHERE id = ? AND (max_redemptions <= 0 OR times_redeemed < max_redemptions)` - rather than a read of TimesRedeemed followed by a separate write, so two concurrent transactions racing the same coupon toward its cap cannot both read the count before either writes it: Postgres takes a row lock on the first UPDATE to reach the row and blocks the second until it commits, at which point the second's WHERE re-evaluates against the already-incremented count under read-committed semantics.
The INSERT of the application row is the FIRST statement in the transaction, matching sqlite's RedeemCoupon (the two are kept structurally identical on purpose). Postgres itself has no equivalent to sqlite's lock-upgrade BUSY, but opening with a read here would still mean two different statement shapes to reason about across backends for no benefit, and the FK on ledger_coupon_applications.coupon_id already gives this insert a cheap, correct way to catch an unknown coupon without a SELECT: it fails with a 23503 foreign-key violation, mapped below to ErrCouponNotFound.
Unknown coupon and exhausted cap share one signal - the conditional UPDATE affects zero rows - and are told apart only if that happens: on postgres this is normally unreachable for an unknown coupon (the FK above already refused the insert), so the existence check here is a defensive fallback for the case where the coupon was deleted between this transaction's insert and its update, not the primary way unknown coupons are caught.
func (*Store) ResumeSubscription ¶
func (s *Store) ResumeSubscription(ctx context.Context, subID id.SubscriptionID, r subscription.Resume) (bool, error)
func (*Store) SetInvoiceProvider ¶
func (*Store) SetSubscriptionProvider ¶
func (*Store) UpdateCoupon ¶
func (*Store) UpdateFeature ¶
func (*Store) UpdateInvoice ¶
func (*Store) UpdateSubscription ¶
func (s *Store) UpdateSubscription(ctx context.Context, sub *subscription.Subscription) error