billing

package
v0.12.1 Latest Latest
Warning

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

Go to latest
Published: Apr 3, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package billing provides shared billing primitives for applications that sell subscriptions and one-off packs through Paddle or Stripe.

The package is intentionally split into a few layers:

  • provider implementations for Paddle and Stripe
  • a Service for summary, checkout, portal, sync, and reconcile flows
  • webhook processors for subscription-state updates and optional credit grants
  • a GORM-backed subscription state repository

A typical integration looks like:

  1. Build a provider with NewPaddleProvider or NewStripeProvider using a data-driven catalog of plans and packs.
  2. Call Migrate and create a SubscriptionStateRepository.
  3. Build a subscription-state webhook processor with NewSubscriptionStatusWebhookProcessor.
  4. Optionally add a credit-grant processor with NewCreditsWebhookProcessor if billing events need to grant app-specific credits.
  5. Combine processors with NewWebhookProcessorChain and expose NewWebhookHandler.
  6. Use Service from your authenticated application endpoints to serve summaries, create checkout sessions, create portal sessions, refresh state, and reconcile completed checkouts.

The shared package stops at backend billing mechanics. Consumer applications still own authentication, HTTP payload shapes, checkout-launch UX, and any mapping from billing events to local ledgers or virtual-currency systems.

See ExampleService and ExampleWebhookHandler for minimal end-to-end wiring.

Index

Examples

Constants

View Source
const (
	PaddleWebhookSignatureHeaderName = paddleWebhookSignatureHeaderName
	StripeWebhookSignatureHeaderName = stripeWebhookSignatureHeaderName

	PaddleMetadataUserEmailKey    = paddleLegacyMetadataUserEmailKey
	PaddleMetadataPurchaseKindKey = paddleLegacyMetadataPurchaseKindKey
	PaddleMetadataPlanCodeKey     = paddleLegacyMetadataPlanCodeKey
	PaddleMetadataPackCodeKey     = paddleLegacyMetadataPackCodeKey
	PaddleMetadataPackCreditsKey  = paddleLegacyMetadataPackCreditsKey

	StripeMetadataUserEmailKey    = stripeLegacyMetadataUserEmailKey
	StripeMetadataPurchaseKindKey = stripeLegacyMetadataPurchaseKindKey
	StripeMetadataPlanCodeKey     = stripeLegacyMetadataPlanCodeKey
	StripeMetadataPackCodeKey     = stripeLegacyMetadataPackCodeKey
	StripeMetadataPackCreditsKey  = stripeLegacyMetadataPackCreditsKey
	StripeMetadataPriceIDKey      = stripeLegacyMetadataPriceIDKey

	PaddlePurchaseKindSubscription = paddlePurchaseKindSubscription
	PaddlePurchaseKindTopUpPack    = paddlePurchaseKindTopUpPack
	StripePurchaseKindSubscription = stripePurchaseKindSubscription
	StripePurchaseKindTopUpPack    = stripePurchaseKindTopUpPack

	PaddleEventTypeTransactionCompleted  = paddleEventTypeTransactionCompleted
	PaddleEventTypeTransactionPaid       = paddleEventTypeTransactionPaid
	PaddleEventTypeTransactionUpdated    = paddleEventTypeTransactionUpdated
	PaddleEventTypeSubscriptionCreated   = paddleEventTypeSubscriptionCreated
	PaddleEventTypeSubscriptionUpdated   = paddleEventTypeSubscriptionUpdated
	PaddleEventTypeSubscriptionCanceled  = paddleEventTypeSubscriptionCanceled
	PaddleEventTypeSubscriptionResumed   = paddleEventTypeSubscriptionResumed
	PaddleEventTypeSubscriptionPaused    = paddleEventTypeSubscriptionPaused
	PaddleEventTypeSubscriptionActivated = paddleEventTypeSubscriptionActivated

	PaddleTransactionStatusPaid      = paddleTransactionStatusPaid
	PaddleTransactionStatusCompleted = paddleTransactionStatusCompleted
	PaddleSubscriptionStatusActive   = paddleSubscriptionStatusActive
	PaddleSubscriptionStatusTrialing = paddleSubscriptionStatusTrialing
	PaddleSubscriptionStatusPaused   = paddleSubscriptionStatusPaused
	PaddleSubscriptionStatusCanceled = paddleSubscriptionStatusCanceled
	PaddleSubscriptionStatusInactive = paddleSubscriptionStatusInactive
	PaddleSubscriptionStatusPastDue  = paddleSubscriptionStatusPastDue

	StripeEventTypeCheckoutSessionCompleted             = stripeEventTypeCheckoutSessionCompleted
	StripeEventTypeCheckoutSessionAsyncPaymentSucceeded = stripeEventTypeCheckoutSessionAsyncPaymentSucceeded
	StripeEventTypeCheckoutSessionAsyncPaymentFailed    = stripeEventTypeCheckoutSessionAsyncPaymentFailed
	StripeEventTypeCheckoutSessionExpired               = stripeEventTypeCheckoutSessionExpired
	StripeEventTypeCheckoutSessionPending               = stripeEventTypeCheckoutSessionPending
	StripeEventTypeSubscriptionCreated                  = stripeEventTypeSubscriptionCreated
	StripeEventTypeSubscriptionUpdated                  = stripeEventTypeSubscriptionUpdated
	StripeEventTypeSubscriptionDeleted                  = stripeEventTypeSubscriptionDeleted

	StripeCheckoutStatusComplete      = stripeCheckoutStatusComplete
	StripeCheckoutPaymentStatusPaid   = stripeCheckoutPaymentStatusPaid
	StripeCheckoutModeSubscription    = stripeCheckoutModeSubscription
	StripeCheckoutModePayment         = stripeCheckoutModePayment
	StripeCheckoutModeSubscriptionRaw = stripeCheckoutModeSubscriptionRaw
	StripeCheckoutModePaymentRaw      = stripeCheckoutModePaymentRaw

	StripeSubscriptionStatusActive            = stripeSubscriptionStatusActive
	StripeSubscriptionStatusTrialing          = stripeSubscriptionStatusTrialing
	StripeSubscriptionStatusPaused            = stripeSubscriptionStatusPaused
	StripeSubscriptionStatusCanceled          = stripeSubscriptionStatusCanceled
	StripeSubscriptionStatusIncomplete        = stripeSubscriptionStatusIncomplete
	StripeSubscriptionStatusIncompleteExpired = stripeSubscriptionStatusIncompleteExpired
	StripeSubscriptionStatusPastDue           = stripeSubscriptionStatusPastDue
	StripeSubscriptionStatusUnpaid            = stripeSubscriptionStatusUnpaid
)
View Source
const (
	ProviderCodePaddle  = "paddle"
	ProviderCodeStripe  = "stripe"
	CheckoutModeOverlay = "overlay"

	PlanCodePro  = "pro"
	PlanCodePlus = "plus"

	PackCodeTopUp     = "top_up"
	PackCodeBulkTopUp = "bulk_top_up"

	PurchaseKindSubscription = "subscription"
	PurchaseKindTopUpPack    = "top_up_pack"
)

Variables

View Source
var (
	ErrPaddleAPIKeyEmpty             = errors.New("billing.paddle.api.key.empty")
	ErrPaddleAPIEnvironmentInvalid   = errors.New("billing.paddle.api.environment.invalid")
	ErrPaddleAPIBaseURLInvalid       = errors.New("billing.paddle.api.base_url.invalid")
	ErrPaddleAPIPaginationInvalid    = errors.New("billing.paddle.api.pagination.invalid")
	ErrPaddleAPIRequestFailed        = errors.New("billing.paddle.api.request.failed")
	ErrPaddleAPITransient            = errors.New("billing.paddle.api.transient")
	ErrPaddleAPIRateLimited          = errors.New("billing.paddle.api.rate_limited")
	ErrPaddleAPICustomerNotFound     = errors.New("billing.paddle.api.customer.not_found")
	ErrPaddleAPITransactionNotFound  = errors.New("billing.paddle.api.transaction.not_found")
	ErrPaddleAPISubscriptionNotFound = errors.New("billing.paddle.api.subscription.not_found")
	ErrPaddleAPIPortalURLNotFound    = errors.New("billing.paddle.api.portal_url.not_found")
	ErrPaddleAPIDefaultCheckoutURL   = errors.New("billing.paddle.api.default_checkout_url.not_set")
	ErrPaddleAPIPriceNotFound        = errors.New("billing.paddle.api.price.not_found")
)
View Source
var (
	ErrPaddleProviderVerifierUnavailable   = errors.New("billing.paddle.provider.verifier.unavailable")
	ErrPaddleProviderAPIKeyEmpty           = errors.New("billing.paddle.provider.api_key.empty")
	ErrPaddleProviderClientTokenEmpty      = errors.New("billing.paddle.provider.client_token.empty")
	ErrPaddleProviderPriceIDEmpty          = errors.New("billing.paddle.provider.price_id.empty")
	ErrPaddleProviderPriceRecurringInvalid = errors.New("billing.paddle.provider.price.recurring.invalid")
	ErrPaddleProviderPriceOneOffInvalid    = errors.New("billing.paddle.provider.price.one_off.invalid")
	ErrPaddleProviderPriceAmountMismatch   = errors.New("billing.paddle.provider.price.amount.mismatch")
	ErrPaddleProviderPlanCreditsMissing    = errors.New("billing.paddle.provider.plan_credits.missing")
	ErrPaddleProviderPlanCreditsInvalid    = errors.New("billing.paddle.provider.plan_credits.invalid")
	ErrPaddleProviderPlanPriceMissing      = errors.New("billing.paddle.provider.plan_price.missing")
	ErrPaddleProviderPlanPriceInvalid      = errors.New("billing.paddle.provider.plan_price.invalid")
	ErrPaddleProviderPackCreditsMissing    = errors.New("billing.paddle.provider.pack_credits.missing")
	ErrPaddleProviderPackCreditsInvalid    = errors.New("billing.paddle.provider.pack_credits.invalid")
	ErrPaddleProviderPackPriceMissing      = errors.New("billing.paddle.provider.pack_price.missing")
	ErrPaddleProviderPackPriceInvalid      = errors.New("billing.paddle.provider.pack_price.invalid")
	ErrPaddleProviderPackPriceIDMissing    = errors.New("billing.paddle.provider.pack_price_id.missing")
	ErrPaddleWebhookPayloadInvalid         = errors.New("billing.paddle.webhook.payload.invalid")
	ErrPaddleProviderClientUnavailable     = errors.New("billing.paddle.provider.client.unavailable")
)
View Source
var (
	ErrPaddleWebhookVerifierUnavailable = errors.New("billing.paddle.webhook.verifier.unavailable")
	ErrPaddleWebhookSecretEmpty         = errors.New("billing.paddle.webhook.secret.empty")
	ErrPaddleWebhookMaxAgeInvalid       = errors.New("billing.paddle.webhook.max_age.invalid")
	ErrPaddleWebhookHeaderInvalid       = errors.New("billing.paddle.webhook.signature_header.invalid")
	ErrPaddleWebhookTimestampInvalid    = errors.New("billing.paddle.webhook.timestamp.invalid")
	ErrPaddleWebhookTimestampExpired    = errors.New("billing.paddle.webhook.timestamp.expired")
	ErrPaddleWebhookSignatureInvalid    = errors.New("billing.paddle.webhook.signature.invalid")
)
View Source
var (
	ErrBillingProviderUnavailable        = errors.New("billing.provider.unavailable")
	ErrBillingUserEmailInvalid           = errors.New("billing.user_email.invalid")
	ErrBillingPlanUnsupported            = errors.New("billing.plan.unsupported")
	ErrBillingSubscriptionManageInPortal = errors.New("billing.subscription.manage_in_portal")
	ErrBillingSubscriptionRequired       = errors.New("billing.subscription.required")
	ErrBillingTopUpPackUnknown           = errors.New("billing.top_up_pack.unknown")
	ErrBillingUserSyncFailed             = errors.New("billing.user_sync.failed")

	// Deprecated aliases kept for compatibility with older callers and tests.
	ErrBillingSubscriptionActive  = ErrBillingSubscriptionManageInPortal
	ErrBillingSubscriptionUpgrade = ErrBillingSubscriptionManageInPortal
)
View Source
var (
	ErrBillingCheckoutReconciliationUnavailable = errors.New("billing.checkout.reconcile.unavailable")
	ErrBillingCheckoutReconciliationUnsupported = errors.New("billing.checkout.reconcile.provider.unsupported")
	ErrBillingCheckoutTransactionInvalid        = errors.New("billing.checkout.transaction.invalid")
	ErrBillingCheckoutTransactionPending        = errors.New("billing.checkout.transaction.pending")
	ErrBillingCheckoutOwnershipMismatch         = errors.New("billing.checkout.ownership.mismatch")
)
View Source
var (
	ErrStripeAPIKeyEmpty                = errors.New("billing.stripe.api.key.empty")
	ErrStripeAPIRequestFailed           = errors.New("billing.stripe.api.request.failed")
	ErrStripeAPITransient               = errors.New("billing.stripe.api.transient")
	ErrStripeAPIRateLimited             = errors.New("billing.stripe.api.rate_limited")
	ErrStripeAPICustomerNotFound        = errors.New("billing.stripe.api.customer.not_found")
	ErrStripeAPICheckoutSessionNotFound = errors.New("billing.stripe.api.checkout_session.not_found")
	ErrStripeAPIPortalURLNotFound       = errors.New("billing.stripe.api.portal_url.not_found")
	ErrStripeAPIPriceNotFound           = errors.New("billing.stripe.api.price.not_found")
)
View Source
var (
	ErrStripeProviderVerifierUnavailable   = errors.New("billing.stripe.provider.verifier.unavailable")
	ErrStripeProviderAPIKeyEmpty           = errors.New("billing.stripe.provider.api_key.empty")
	ErrStripeProviderClientTokenEmpty      = errors.New("billing.stripe.provider.client_token.empty")
	ErrStripeProviderURLInvalid            = errors.New("billing.stripe.provider.url.invalid")
	ErrStripeProviderPriceIDEmpty          = errors.New("billing.stripe.provider.price_id.empty")
	ErrStripeProviderPlanCreditsMissing    = errors.New("billing.stripe.provider.plan_credits.missing")
	ErrStripeProviderPlanCreditsInvalid    = errors.New("billing.stripe.provider.plan_credits.invalid")
	ErrStripeProviderPlanPriceMissing      = errors.New("billing.stripe.provider.plan_price.missing")
	ErrStripeProviderPlanPriceInvalid      = errors.New("billing.stripe.provider.plan_price.invalid")
	ErrStripeProviderPackCreditsMissing    = errors.New("billing.stripe.provider.pack_credits.missing")
	ErrStripeProviderPackCreditsInvalid    = errors.New("billing.stripe.provider.pack_credits.invalid")
	ErrStripeProviderPackPriceMissing      = errors.New("billing.stripe.provider.pack_price.missing")
	ErrStripeProviderPackPriceInvalid      = errors.New("billing.stripe.provider.pack_price.invalid")
	ErrStripeProviderPackPriceIDMissing    = errors.New("billing.stripe.provider.pack_price_id.missing")
	ErrStripeProviderPriceRecurringInvalid = errors.New("billing.stripe.provider.price.recurring.invalid")
	ErrStripeProviderPriceOneOffInvalid    = errors.New("billing.stripe.provider.price.one_off.invalid")
	ErrStripeProviderPriceAmountMismatch   = errors.New("billing.stripe.provider.price.amount.mismatch")
	ErrStripeWebhookPayloadInvalid         = errors.New("billing.stripe.webhook.payload.invalid")
	ErrStripeProviderClientUnavailable     = errors.New("billing.stripe.provider.client.unavailable")
)
View Source
var (
	ErrStripeWebhookVerifierUnavailable = errors.New("billing.stripe.webhook.verifier.unavailable")
	ErrStripeWebhookSecretEmpty         = errors.New("billing.stripe.webhook.secret.empty")
	ErrStripeWebhookMaxAgeInvalid       = errors.New("billing.stripe.webhook.max_age.invalid")
	ErrStripeWebhookHeaderInvalid       = errors.New("billing.stripe.webhook.signature_header.invalid")
	ErrStripeWebhookTimestampInvalid    = errors.New("billing.stripe.webhook.timestamp.invalid")
	ErrStripeWebhookTimestampExpired    = errors.New("billing.stripe.webhook.timestamp.expired")
	ErrStripeWebhookSignatureInvalid    = errors.New("billing.stripe.webhook.signature.invalid")
)
View Source
var (
	ErrBillingSubscriptionStateRepositoryUnavailable = errors.New("billing.subscription_state.repository.unavailable")
	ErrBillingSubscriptionStateProviderInvalid       = errors.New("billing.subscription_state.provider.invalid")
	ErrBillingSubscriptionStateUserEmailInvalid      = errors.New("billing.subscription_state.user_email.invalid")
	ErrBillingSubscriptionStateSubscriptionIDInvalid = errors.New("billing.subscription_state.subscription_id.invalid")
	ErrBillingSubscriptionStateStatusInvalid         = errors.New("billing.subscription_state.status.invalid")
)
View Source
var (
	ErrWebhookSubscriptionStateRepositoryUnavailable = errors.New("billing.webhook.subscription_state.repository.unavailable")
	ErrWebhookSubscriptionStateProviderUnsupported   = errors.New("billing.webhook.subscription_state.provider.unsupported")
)
View Source
var (
	ErrWebhookGrantResolverUnavailable         = errors.New("billing.webhook.grant_resolver.unavailable")
	ErrWebhookGrantResolverProviderUnavailable = errors.New("billing.webhook.grant_resolver.provider.unavailable")
	ErrWebhookGrantResolverProviderUnsupported = errors.New("billing.webhook.grant_resolver.provider.unsupported")
	ErrWebhookCreditsServiceUnavailable        = errors.New("billing.webhook.credits_service.unavailable")
	ErrWebhookGrantPayloadInvalid              = errors.New("billing.webhook.payload.invalid")
	ErrWebhookGrantMetadataInvalid             = errors.New("billing.webhook.metadata.invalid")
	ErrWebhookGrantPlanUnknown                 = errors.New("billing.webhook.plan.unknown")
	ErrWebhookGrantPackUnknown                 = errors.New("billing.webhook.pack.unknown")
)
View Source
var ErrDuplicateGrant = errors.New("billing: duplicate grant")

ErrDuplicateGrant signals an idempotent duplicate — not a failure.

View Source
var ErrGrantRecipientUnresolved = errors.New("billing: grant recipient unresolved")

ErrGrantRecipientUnresolved signals that the event could not be mapped to a recipient.

View Source
var ErrHTTPRetryAttemptsExhausted = errors.New("billing.http.retry.attempts.exhausted")

Functions

func Migrate

func Migrate(ctx context.Context, database *gorm.DB) error

Migrate creates or updates the billing subscription-state table.

func NormalizePackCode

func NormalizePackCode(rawPackCode string) string

NormalizePackCode lowercases and trims application pack identifiers before they are compared or stored in metadata.

func NormalizePurchaseKind

func NormalizePurchaseKind(rawPurchaseKind string) string

NormalizePurchaseKind lowercases and trims a purchase-kind marker from provider metadata.

func PackLabelForCode

func PackLabelForCode(rawPackCode string) string

PackLabelForCode returns the built-in display label for package-defined pack codes. Applications with custom catalogs can supply their own labels instead.

Types

type CatalogValidationProvider

type CatalogValidationProvider interface {
	ValidateCatalog(context.Context) error
}

CatalogValidationProvider validates configured price IDs and amounts against the remote provider catalog.

type CheckoutEventStatus

type CheckoutEventStatus string

CheckoutEventStatus classifies a checkout event for shared webhook and reconcile logic.

const (
	CheckoutEventStatusUnknown   CheckoutEventStatus = "unknown"
	CheckoutEventStatusPending   CheckoutEventStatus = "pending"
	CheckoutEventStatusSucceeded CheckoutEventStatus = "succeeded"
	CheckoutEventStatusFailed    CheckoutEventStatus = "failed"
	CheckoutEventStatusExpired   CheckoutEventStatus = "expired"
)

type CheckoutEventStatusProvider

type CheckoutEventStatusProvider interface {
	ResolveCheckoutEventStatus(string) CheckoutEventStatus
}

CheckoutEventStatusProvider maps provider-native event types to CheckoutEventStatus values.

type CheckoutReconcileProvider

type CheckoutReconcileProvider interface {
	BuildCheckoutReconcileEvent(context.Context, string) (WebhookEvent, string, error)
}

CheckoutReconcileProvider can turn a completed checkout identifier into a synthetic webhook event that the shared pipeline can process.

type CheckoutSession

type CheckoutSession struct {
	ProviderCode  string
	TransactionID string
	CheckoutMode  string
}

CheckoutSession is the provider-neutral result of a checkout creation call. TransactionID is later used by provider-specific frontends or reconcile flows to finish the purchase UX.

type CommerceProvider

type CommerceProvider interface {
	Code() string
	SubscriptionPlans() []SubscriptionPlan
	TopUpPacks() []TopUpPack
	PublicConfig() PublicConfig
	BuildUserSyncEvents(context.Context, string) ([]WebhookEvent, error)
	CreateSubscriptionCheckout(context.Context, CustomerContext, string) (CheckoutSession, error)
	CreateTopUpCheckout(context.Context, CustomerContext, string) (CheckoutSession, error)
	CreateCustomerPortalSession(context.Context, string) (PortalSession, error)
}

CommerceProvider is the shared provider contract consumed by Service. Custom applications usually use a package-supplied PaddleProvider or StripeProvider rather than implementing this interface themselves.

type CreditGrantInput

type CreditGrantInput struct {
	UserEmail      string
	SubjectID      string
	Credits        int64
	IdempotencyKey string
	Reason         string
	Reference      string
	Metadata       map[string]string
}

CreditGrantInput contains the data needed to grant credits from a billing event.

type CreditGranter

type CreditGranter interface {
	GrantBillingCredits(ctx context.Context, input CreditGrantInput) error
}

CreditGranter is the interface that consuming applications implement to bridge billing webhook grants to their credit/ledger system.

type CustomerContext added in v0.12.0

type CustomerContext struct {
	Email     string
	SubjectID string
}

CustomerContext identifies the checkout owner. Email is currently the canonical key for summary, portal, sync, and reconcile flows; SubjectID is carried through checkout metadata for app-level identity mapping.

func NormalizeCustomerContext added in v0.12.0

func NormalizeCustomerContext(customer CustomerContext) CustomerContext

NormalizeCustomerContext trims SubjectID and normalizes Email for provider calls and metadata generation.

type HTTPErrorDescriptor

type HTTPErrorDescriptor struct {
	StatusCode int
	Message    string
}

func ResolveHTTPErrorDescriptor

func ResolveHTTPErrorDescriptor(providerCode string, err error) HTTPErrorDescriptor

ResolveHTTPErrorDescriptor maps package and provider errors to a stable HTTP status code and machine-readable message for app handlers.

type PackCatalogItem added in v0.12.0

type PackCatalogItem struct {
	Code       string
	Label      string
	PriceID    string
	Credits    int64
	PriceCents int64
}

PackCatalogItem configures a one-off pack when constructing a provider.

type PaddleCommerceClient added in v0.12.0

type PaddleCommerceClient = paddleCommerceClient

type PaddlePriceBillingCycle added in v0.12.0

type PaddlePriceBillingCycle = paddlePriceBillingCycle

type PaddlePriceDetails added in v0.12.0

type PaddlePriceDetails = paddlePriceDetails

type PaddleProvider

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

func NewPaddleProvider

func NewPaddleProvider(
	settings PaddleProviderSettings,
	verifier PaddleSignatureVerifier,
	client PaddleCommerceClient,
) (*PaddleProvider, error)

NewPaddleProvider constructs a Paddle-backed Provider from application settings and a webhook signature verifier.

func (*PaddleProvider) BuildCheckoutReconcileEvent

func (provider *PaddleProvider) BuildCheckoutReconcileEvent(
	ctx context.Context,
	transactionID string,
) (WebhookEvent, string, error)

func (*PaddleProvider) BuildUserSyncEvents

func (provider *PaddleProvider) BuildUserSyncEvents(
	ctx context.Context,
	userEmail string,
) ([]WebhookEvent, error)

func (*PaddleProvider) ClientToken

func (provider *PaddleProvider) ClientToken() string

func (*PaddleProvider) Code

func (provider *PaddleProvider) Code() string

func (*PaddleProvider) CreateCustomerPortalSession

func (provider *PaddleProvider) CreateCustomerPortalSession(
	ctx context.Context,
	userEmail string,
) (PortalSession, error)

func (*PaddleProvider) CreateSubscriptionCheckout

func (provider *PaddleProvider) CreateSubscriptionCheckout(
	ctx context.Context,
	customer CustomerContext,
	planCode string,
) (CheckoutSession, error)

func (*PaddleProvider) CreateTopUpCheckout

func (provider *PaddleProvider) CreateTopUpCheckout(
	ctx context.Context,
	customer CustomerContext,
	packCode string,
) (CheckoutSession, error)

func (*PaddleProvider) Environment

func (provider *PaddleProvider) Environment() string

func (*PaddleProvider) InspectSubscriptions added in v0.12.0

func (provider *PaddleProvider) InspectSubscriptions(
	ctx context.Context,
	userEmail string,
) ([]ProviderSubscription, error)

func (*PaddleProvider) NewSubscriptionStatusWebhookProcessor

func (provider *PaddleProvider) NewSubscriptionStatusWebhookProcessor(
	stateRepository SubscriptionStateRepository,
) (WebhookProcessor, error)

func (*PaddleProvider) NewWebhookGrantResolver

func (provider *PaddleProvider) NewWebhookGrantResolver() (WebhookGrantResolver, error)

func (*PaddleProvider) ParseWebhookEvent

func (provider *PaddleProvider) ParseWebhookEvent(payload []byte) (WebhookEventMetadata, error)

func (*PaddleProvider) PublicConfig

func (provider *PaddleProvider) PublicConfig() PublicConfig

func (*PaddleProvider) ResolveCheckoutEventStatus

func (provider *PaddleProvider) ResolveCheckoutEventStatus(eventType string) CheckoutEventStatus

func (*PaddleProvider) SignatureHeaderName

func (provider *PaddleProvider) SignatureHeaderName() string

func (*PaddleProvider) SubscriptionPlans

func (provider *PaddleProvider) SubscriptionPlans() []SubscriptionPlan

func (*PaddleProvider) TopUpPacks

func (provider *PaddleProvider) TopUpPacks() []TopUpPack

func (*PaddleProvider) ValidateCatalog

func (provider *PaddleProvider) ValidateCatalog(ctx context.Context) error

func (*PaddleProvider) VerifySignature

func (provider *PaddleProvider) VerifySignature(signatureHeader string, payload []byte) error

type PaddleProviderSettings

type PaddleProviderSettings struct {
	Environment                string
	APIBaseURL                 string
	APIKey                     string
	ClientToken                string
	Plans                      []PlanCatalogItem
	Packs                      []PackCatalogItem
	ProMonthlyPriceID          string
	PlusMonthlyPriceID         string
	SubscriptionMonthlyCredits map[string]int64
	SubscriptionMonthlyPrices  map[string]int64
	TopUpPackPriceIDs          map[string]string
	TopUpPackCredits           map[string]int64
	TopUpPackPrices            map[string]int64
}

PaddleProviderSettings configures a Paddle-backed provider and its plan/pack catalog.

type PaddleSignatureVerifier

type PaddleSignatureVerifier interface {
	Verify(signatureHeader string, payload []byte) error
}

PaddleSignatureVerifier verifies Paddle webhook signatures.

type PaddleSubscriptionWebhookData added in v0.12.0

type PaddleSubscriptionWebhookData = paddleSubscriptionWebhookData

type PaddleSubscriptionWebhookItem added in v0.12.0

type PaddleSubscriptionWebhookItem = paddleSubscriptionWebhookItem

type PaddleSubscriptionWebhookItemPrice added in v0.12.0

type PaddleSubscriptionWebhookItemPrice = paddleSubscriptionWebhookItemPrice

type PaddleSubscriptionWebhookPayload added in v0.12.0

type PaddleSubscriptionWebhookPayload = paddleSubscriptionWebhookPayload

type PaddleTransactionCompletedLineDetails added in v0.12.0

type PaddleTransactionCompletedLineDetails = paddleTransactionCompletedLineDetails

type PaddleTransactionCompletedLineItem added in v0.12.0

type PaddleTransactionCompletedLineItem = paddleTransactionCompletedLineItem

type PaddleTransactionCompletedLineItemPrice added in v0.12.0

type PaddleTransactionCompletedLineItemPrice = paddleTransactionCompletedLineItemPrice

type PaddleTransactionCompletedWebhookData added in v0.12.0

type PaddleTransactionCompletedWebhookData = paddleTransactionCompletedWebhookData

type PaddleTransactionCompletedWebhookPayload added in v0.12.0

type PaddleTransactionCompletedWebhookPayload = paddleTransactionCompletedWebhookPayload

type PaddleTransactionInput added in v0.12.0

type PaddleTransactionInput = paddleTransactionInput

type PaddleWebhookEnvelope

type PaddleWebhookEnvelope struct {
	EventID    string `json:"event_id"`
	EventType  string `json:"event_type"`
	OccurredAt string `json:"occurred_at"`
}

PaddleWebhookEnvelope represents the top-level structure of Paddle webhook notifications.

type PaddleWebhookVerifier

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

PaddleWebhookVerifier verifies Paddle webhook signatures.

func NewPaddleWebhookVerifier

func NewPaddleWebhookVerifier(webhookSecret string, maxAge time.Duration) (*PaddleWebhookVerifier, error)

NewPaddleWebhookVerifier constructs a verifier with a webhook secret and timestamp tolerance.

func (*PaddleWebhookVerifier) Verify

func (verifier *PaddleWebhookVerifier) Verify(signatureHeader string, payload []byte) error

Verify validates Paddle-Signature against payload bytes.

type PlanCatalogItem added in v0.12.0

type PlanCatalogItem struct {
	Code           string
	Label          string
	PriceID        string
	MonthlyCredits int64
	PriceCents     int64
}

PlanCatalogItem configures a recurring plan when constructing a provider.

type PortalSession

type PortalSession struct {
	ProviderCode string
	URL          string
}

PortalSession contains the provider-hosted customer-portal URL for the authenticated billing owner.

type Provider

type Provider interface {
	WebhookProvider
	CommerceProvider
}

Provider is the full provider contract used by webhook handlers. It combines the CommerceProvider and WebhookProvider capabilities.

type ProviderSubscription added in v0.12.0

type ProviderSubscription struct {
	SubscriptionID string
	PlanCode       string
	Status         string
	ProviderStatus string
	NextBillingAt  time.Time
	OccurredAt     time.Time
}

ProviderSubscription is the normalized live subscription shape returned by provider inspection APIs and sync flows.

type PublicConfig

type PublicConfig struct {
	ProviderCode string
	Environment  string
	ClientToken  string
}

PublicConfig exposes the provider settings a frontend needs to launch the provider's hosted checkout experience.

type Service

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

Service coordinates app-facing billing operations on top of a shared CommerceProvider, optional subscription-state persistence, and optional webhook processing.

Example
package main

import (
	"context"
	"fmt"

	"github.com/tyemirov/utils/billing"
)

type exampleCommerceProvider struct{}

func (exampleCommerceProvider) Code() string {
	return billing.ProviderCodePaddle
}

func (exampleCommerceProvider) SubscriptionPlans() []billing.SubscriptionPlan {
	return []billing.SubscriptionPlan{
		{
			Code:           "pro",
			Label:          "Pro",
			MonthlyCredits: 1000,
			PriceDisplay:   "$10.00",
			BillingPeriod:  "monthly",
		},
	}
}

func (exampleCommerceProvider) TopUpPacks() []billing.TopUpPack {
	return nil
}

func (exampleCommerceProvider) PublicConfig() billing.PublicConfig {
	return billing.PublicConfig{
		ProviderCode: billing.ProviderCodePaddle,
		Environment:  "sandbox",
		ClientToken:  "client_token_demo",
	}
}

func (exampleCommerceProvider) BuildUserSyncEvents(context.Context, string) ([]billing.WebhookEvent, error) {
	return nil, nil
}

func (exampleCommerceProvider) CreateSubscriptionCheckout(
	context.Context,
	billing.CustomerContext,
	string,
) (billing.CheckoutSession, error) {
	return billing.CheckoutSession{
		ProviderCode:  billing.ProviderCodePaddle,
		TransactionID: "txn_123",
		CheckoutMode:  billing.CheckoutModeOverlay,
	}, nil
}

func (exampleCommerceProvider) CreateTopUpCheckout(
	context.Context,
	billing.CustomerContext,
	string,
) (billing.CheckoutSession, error) {
	return billing.CheckoutSession{}, billing.ErrBillingTopUpPackUnknown
}

func (exampleCommerceProvider) CreateCustomerPortalSession(
	context.Context,
	string,
) (billing.PortalSession, error) {
	return billing.PortalSession{
		ProviderCode: billing.ProviderCodePaddle,
		URL:          "https://billing.example.test/portal",
	}, nil
}

type exampleSubscriptionStateRepository struct {
	states map[string]billing.SubscriptionState
}

func (repository *exampleSubscriptionStateRepository) Upsert(
	_ context.Context,
	input billing.SubscriptionStateUpsertInput,
) error {
	if repository.states == nil {
		repository.states = map[string]billing.SubscriptionState{}
	}
	repository.states[input.UserEmail] = billing.SubscriptionState{
		ProviderCode:        input.ProviderCode,
		UserEmail:           input.UserEmail,
		Status:              input.Status,
		ProviderStatus:      input.ProviderStatus,
		ActivePlan:          input.ActivePlan,
		SubscriptionID:      input.SubscriptionID,
		NextBillingAt:       input.NextBillingAt,
		LastEventID:         input.LastEventID,
		LastEventType:       input.LastEventType,
		LastEventOccurredAt: input.EventOccurredAt,
		LastTransactionID:   input.LastTransactionID,
	}
	return nil
}

func (repository *exampleSubscriptionStateRepository) Get(
	_ context.Context,
	_ string,
	userEmail string,
) (billing.SubscriptionState, bool, error) {
	state, found := repository.states[userEmail]
	return state, found, nil
}

func (repository *exampleSubscriptionStateRepository) GetBySubscriptionID(
	_ context.Context,
	_ string,
	subscriptionID string,
) (billing.SubscriptionState, bool, error) {
	for _, state := range repository.states {
		if state.SubscriptionID == subscriptionID {
			return state, true, nil
		}
	}
	return billing.SubscriptionState{}, false, nil
}

func main() {
	repository := &exampleSubscriptionStateRepository{
		states: map[string]billing.SubscriptionState{
			"subscriber@example.com": {
				ProviderCode:   billing.ProviderCodePaddle,
				UserEmail:      "subscriber@example.com",
				Status:         "active",
				ProviderStatus: "active",
				ActivePlan:     "pro",
				SubscriptionID: "sub_123",
			},
		},
	}
	service := billing.NewService(exampleCommerceProvider{}, repository)

	summary, _ := service.GetSubscriptionSummary(context.Background(), "subscriber@example.com")
	fmt.Println(summary.ProviderCode, summary.Status, summary.ActivePlan)

	checkout, _ := service.CreateSubscriptionCheckout(
		context.Background(),
		billing.CustomerContext{Email: "new@example.com"},
		"pro",
	)
	fmt.Println(checkout.ProviderCode, checkout.TransactionID, checkout.CheckoutMode)

}
Output:
paddle active pro
paddle txn_123 overlay

func NewService

func NewService(provider CommerceProvider, subscriptionStateReaders ...SubscriptionStateRepository) *Service

NewService builds a Service for summary, checkout, portal, sync, and reconcile operations. When a repository is provided, the service can persist and query normalized subscription state.

func NewServiceWithWebhookProcessor

func NewServiceWithWebhookProcessor(
	provider CommerceProvider,
	webhookProcessor WebhookProcessor,
	subscriptionStateReaders ...SubscriptionStateRepository,
) *Service

NewServiceWithWebhookProcessor is like NewService but also wires the processor used by sync and reconcile flows.

func (*Service) CreatePortalSession

func (service *Service) CreatePortalSession(ctx context.Context, userEmail string) (PortalSession, error)

CreatePortalSession creates a provider-hosted customer portal for the given billing owner.

func (*Service) CreateSubscriptionCheckout

func (service *Service) CreateSubscriptionCheckout(
	ctx context.Context,
	customer CustomerContext,
	planCode string,
) (CheckoutSession, error)

CreateSubscriptionCheckout validates the requested plan and creates a provider checkout session for the normalized customer.

func (*Service) CreateTopUpCheckout

func (service *Service) CreateTopUpCheckout(
	ctx context.Context,
	customer CustomerContext,
	packCode string,
) (CheckoutSession, error)

CreateTopUpCheckout validates top-up eligibility and creates a provider checkout session for the requested pack.

func (*Service) GetSubscriptionSummary

func (service *Service) GetSubscriptionSummary(ctx context.Context, userEmail string) (SubscriptionSummary, error)

GetSubscriptionSummary returns the caller's normalized subscription state and public billing catalog. The state lookup is keyed by normalized user email.

func (*Service) ProviderCode

func (service *Service) ProviderCode() string

ProviderCode returns the billing provider code for the configured service.

func (*Service) ReconcileCheckout

func (service *Service) ReconcileCheckout(
	ctx context.Context,
	userEmail string,
	transactionID string,
) error

ReconcileCheckout asks the provider for the current state of a checkout and replays it through the configured webhook processor. This is useful after a user returns from checkout but before the webhook is observed locally.

func (*Service) SyncUserBillingEvents

func (service *Service) SyncUserBillingEvents(ctx context.Context, userEmail string) error

SyncUserBillingEvents asks the provider for synthetic sync events for the given user and replays them through the configured webhook processor.

func (*Service) WithTopUpEligibilityPolicy added in v0.12.0

func (service *Service) WithTopUpEligibilityPolicy(policy TopUpEligibilityPolicy) *Service

WithTopUpEligibilityPolicy overrides the shared top-up gating behavior and returns the same service for fluent setup.

type StripeCheckoutSessionInput added in v0.12.0

type StripeCheckoutSessionInput = stripeCheckoutSessionInput

type StripeCheckoutSessionWebhookData added in v0.12.0

type StripeCheckoutSessionWebhookData = stripeCheckoutSessionWebhookData

type StripeCheckoutSessionWebhookPayload added in v0.12.0

type StripeCheckoutSessionWebhookPayload = stripeCheckoutSessionWebhookPayload

type StripeCheckoutSessionWebhookPayloadData added in v0.12.0

type StripeCheckoutSessionWebhookPayloadData = stripeCheckoutSessionWebhookPayloadData

type StripeCommerceClient added in v0.12.0

type StripeCommerceClient = stripeCommerceClient

type StripePortalSessionInput added in v0.12.0

type StripePortalSessionInput = stripePortalSessionInput

type StripePriceRecurring added in v0.12.0

type StripePriceRecurring = stripePriceRecurring

type StripePriceResponse added in v0.12.0

type StripePriceResponse = stripePriceResponse

type StripeProvider

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

func NewStripeProvider

func NewStripeProvider(
	settings StripeProviderSettings,
	verifier StripeSignatureVerifier,
	client StripeCommerceClient,
) (*StripeProvider, error)

NewStripeProvider constructs a Stripe-backed Provider from application settings and a webhook signature verifier.

func (*StripeProvider) BuildCheckoutReconcileEvent

func (provider *StripeProvider) BuildCheckoutReconcileEvent(
	ctx context.Context,
	transactionID string,
) (WebhookEvent, string, error)

func (*StripeProvider) BuildUserSyncEvents

func (provider *StripeProvider) BuildUserSyncEvents(ctx context.Context, userEmail string) ([]WebhookEvent, error)

func (*StripeProvider) Code

func (provider *StripeProvider) Code() string

func (*StripeProvider) CreateCustomerPortalSession

func (provider *StripeProvider) CreateCustomerPortalSession(
	ctx context.Context,
	userEmail string,
) (PortalSession, error)

func (*StripeProvider) CreateSubscriptionCheckout

func (provider *StripeProvider) CreateSubscriptionCheckout(
	ctx context.Context,
	customer CustomerContext,
	planCode string,
) (CheckoutSession, error)

func (*StripeProvider) CreateTopUpCheckout

func (provider *StripeProvider) CreateTopUpCheckout(
	ctx context.Context,
	customer CustomerContext,
	packCode string,
) (CheckoutSession, error)

func (*StripeProvider) InspectSubscriptions added in v0.12.0

func (provider *StripeProvider) InspectSubscriptions(
	ctx context.Context,
	userEmail string,
) ([]ProviderSubscription, error)

func (*StripeProvider) NewSubscriptionStatusWebhookProcessor

func (provider *StripeProvider) NewSubscriptionStatusWebhookProcessor(
	stateRepository SubscriptionStateRepository,
) (WebhookProcessor, error)

func (*StripeProvider) NewWebhookGrantResolver

func (provider *StripeProvider) NewWebhookGrantResolver() (WebhookGrantResolver, error)

func (*StripeProvider) ParseWebhookEvent

func (provider *StripeProvider) ParseWebhookEvent(payload []byte) (WebhookEventMetadata, error)

func (*StripeProvider) PublicConfig

func (provider *StripeProvider) PublicConfig() PublicConfig

func (*StripeProvider) ResolveCheckoutEventStatus

func (provider *StripeProvider) ResolveCheckoutEventStatus(eventType string) CheckoutEventStatus

func (*StripeProvider) SignatureHeaderName

func (provider *StripeProvider) SignatureHeaderName() string

func (*StripeProvider) SubscriptionPlans

func (provider *StripeProvider) SubscriptionPlans() []SubscriptionPlan

func (*StripeProvider) TopUpPacks

func (provider *StripeProvider) TopUpPacks() []TopUpPack

func (*StripeProvider) ValidateCatalog

func (provider *StripeProvider) ValidateCatalog(ctx context.Context) error

func (*StripeProvider) VerifySignature

func (provider *StripeProvider) VerifySignature(signatureHeader string, payload []byte) error

type StripeProviderSettings

type StripeProviderSettings struct {
	Environment                string
	APIKey                     string
	ClientToken                string
	CheckoutSuccessURL         string
	CheckoutCancelURL          string
	PortalReturnURL            string
	Plans                      []PlanCatalogItem
	Packs                      []PackCatalogItem
	ProMonthlyPriceID          string
	PlusMonthlyPriceID         string
	SubscriptionMonthlyCredits map[string]int64
	SubscriptionMonthlyPrices  map[string]int64
	TopUpPackPriceIDs          map[string]string
	TopUpPackCredits           map[string]int64
	TopUpPackPrices            map[string]int64
}

StripeProviderSettings configures a Stripe-backed provider and its plan/pack catalog.

type StripeSignatureVerifier

type StripeSignatureVerifier interface {
	Verify(signatureHeader string, payload []byte) error
}

StripeSignatureVerifier verifies Stripe webhook signatures.

type StripeSubscriptionItem added in v0.12.0

type StripeSubscriptionItem = stripeSubscriptionItem

type StripeSubscriptionItemPrice added in v0.12.0

type StripeSubscriptionItemPrice = stripeSubscriptionItemPrice

type StripeSubscriptionItems added in v0.12.0

type StripeSubscriptionItems = stripeSubscriptionItems

type StripeSubscriptionWebhookData added in v0.12.0

type StripeSubscriptionWebhookData = stripeSubscriptionWebhookData

type StripeSubscriptionWebhookPayload added in v0.12.0

type StripeSubscriptionWebhookPayload = stripeSubscriptionWebhookPayload

type StripeSubscriptionWebhookPayloadData added in v0.12.0

type StripeSubscriptionWebhookPayloadData = stripeSubscriptionWebhookPayloadData

type StripeWebhookVerifier

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

func NewStripeWebhookVerifier

func NewStripeWebhookVerifier(webhookSecret string, maxAge time.Duration) (*StripeWebhookVerifier, error)

func (*StripeWebhookVerifier) Verify

func (verifier *StripeWebhookVerifier) Verify(signatureHeader string, payload []byte) error

type SubscriptionInspector added in v0.12.0

type SubscriptionInspector interface {
	InspectSubscriptions(context.Context, string) ([]ProviderSubscription, error)
}

SubscriptionInspector exposes a provider's live subscription inspection API to the shared Service.

type SubscriptionPlan

type SubscriptionPlan struct {
	Code           string `json:"code"`
	Label          string `json:"label"`
	MonthlyCredits int64  `json:"monthly_credits"`
	PriceDisplay   string `json:"price_display,omitempty"`
	BillingPeriod  string `json:"billing_period,omitempty"`
}

SubscriptionPlan is the public catalog entry returned to applications for a recurring subscription product.

type SubscriptionState

type SubscriptionState struct {
	ProviderCode        string
	UserEmail           string
	Status              string
	ProviderStatus      string
	ActivePlan          string
	SubscriptionID      string
	NextBillingAt       time.Time
	LastEventID         string
	LastEventType       string
	LastEventOccurredAt time.Time
	LastTransactionID   string
	UpdatedAt           time.Time
}

SubscriptionState is the normalized persisted billing state for a user and provider pair.

type SubscriptionStateRepository

type SubscriptionStateRepository interface {
	Upsert(context.Context, SubscriptionStateUpsertInput) error
	Get(context.Context, string, string) (SubscriptionState, bool, error)
	GetBySubscriptionID(context.Context, string, string) (SubscriptionState, bool, error)
}

SubscriptionStateRepository persists normalized subscription state keyed by provider code and normalized user email.

func NewSubscriptionStateRepository

func NewSubscriptionStateRepository(database *gorm.DB) SubscriptionStateRepository

NewSubscriptionStateRepository returns the package's GORM-backed repository implementation.

type SubscriptionStateUpsertInput

type SubscriptionStateUpsertInput struct {
	ProviderCode      string
	UserEmail         string
	Status            string
	ProviderStatus    string
	ActivePlan        string
	SubscriptionID    string
	NextBillingAt     time.Time
	LastEventID       string
	LastEventType     string
	EventOccurredAt   time.Time
	LastTransactionID string
}

SubscriptionStateUpsertInput is the normalized state snapshot written after a webhook event, sync event, or live inspection.

type SubscriptionStatusWebhookProcessorProvider

type SubscriptionStatusWebhookProcessorProvider interface {
	NewSubscriptionStatusWebhookProcessor(SubscriptionStateRepository) (WebhookProcessor, error)
}

SubscriptionStatusWebhookProcessorProvider exposes a provider-native subscription-state reducer for webhook events and synthetic sync events.

type SubscriptionSummary

type SubscriptionSummary struct {
	ProviderCode        string
	Enabled             bool
	Status              string
	ProviderStatus      string
	IsDelinquent        bool
	DelinquentReason    string
	ActivePlan          string
	SubscriptionID      string
	NextBillingAt       time.Time
	LastEventType       string
	LastEventOccurredAt time.Time
	Environment         string
	ClientToken         string
	Plans               []SubscriptionPlan
	TopUpPacks          []TopUpPack
}

SubscriptionSummary is the shared backend view of a user's current billing state and the public catalog they can purchase from.

type TopUpEligibilityPolicy added in v0.12.0

type TopUpEligibilityPolicy string

TopUpEligibilityPolicy controls whether top-up purchases require an active subscription according to the shared Service rules.

const (
	TopUpEligibilityPolicyRequiresActiveSubscription TopUpEligibilityPolicy = "requires_active_subscription"
	TopUpEligibilityPolicyUnrestricted               TopUpEligibilityPolicy = "unrestricted"
)

func NormalizeTopUpEligibilityPolicy added in v0.12.0

func NormalizeTopUpEligibilityPolicy(rawPolicy TopUpEligibilityPolicy) TopUpEligibilityPolicy

NormalizeTopUpEligibilityPolicy converts an arbitrary value into one of the supported policy constants, defaulting to subscription-gated behavior.

type TopUpPack

type TopUpPack struct {
	Code          string `json:"code"`
	Label         string `json:"label"`
	Credits       int64  `json:"credits"`
	PriceDisplay  string `json:"price_display,omitempty"`
	BillingPeriod string `json:"billing_period,omitempty"`
}

TopUpPack is the public catalog entry returned to applications for a one-off purchasable pack.

type WebhookEvent

type WebhookEvent struct {
	ProviderCode string
	EventID      string
	EventType    string
	OccurredAt   time.Time
	Payload      []byte
}

WebhookEvent is the normalized event passed through the package's webhook processing pipeline.

type WebhookEventMetadata

type WebhookEventMetadata struct {
	EventID    string
	EventType  string
	OccurredAt time.Time
}

WebhookEventMetadata is the minimal event envelope extracted during webhook parsing before the full payload is handed to processors.

type WebhookGrant

type WebhookGrant struct {
	UserEmail string
	SubjectID string
	Credits   int64
	Reason    string
	Reference string
	Metadata  map[string]string
}

WebhookGrant is the normalized grant payload resolved from a provider event.

type WebhookGrantResolver

type WebhookGrantResolver interface {
	Resolve(context.Context, WebhookEvent) (WebhookGrant, bool, error)
}

WebhookGrantResolver resolves provider-native webhook payloads into package-level grant instructions.

func NewWebhookGrantResolver

func NewWebhookGrantResolver(provider CommerceProvider) (WebhookGrantResolver, error)

NewWebhookGrantResolver returns the provider-native grant resolver used by the shared credit processor.

type WebhookGrantResolverProvider

type WebhookGrantResolverProvider interface {
	NewWebhookGrantResolver() (WebhookGrantResolver, error)
}

WebhookGrantResolverProvider exposes provider-native webhook grant resolution for use in the shared grant processor.

type WebhookHandler

type WebhookHandler struct {
	Provider  WebhookProvider
	Processor WebhookProcessor
}

WebhookHandler is an http.Handler that verifies signatures, parses provider payloads, and forwards normalized events into a WebhookProcessor.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"time"

	"github.com/tyemirov/utils/billing"
)

type exampleWebhookProvider struct{}

func (exampleWebhookProvider) Code() string {
	return billing.ProviderCodePaddle
}

func (exampleWebhookProvider) SignatureHeaderName() string {
	return "X-Test-Signature"
}

func (exampleWebhookProvider) VerifySignature(signatureHeader string, _ []byte) error {
	if strings.TrimSpace(signatureHeader) != "ok" {
		return errors.New("invalid signature")
	}
	return nil
}

func (exampleWebhookProvider) ParseWebhookEvent(_ []byte) (billing.WebhookEventMetadata, error) {
	return billing.WebhookEventMetadata{
		EventID:    "evt_123",
		EventType:  "subscription.updated",
		OccurredAt: time.Unix(1700000000, 0).UTC(),
	}, nil
}

func main() {
	processedEventType := ""
	handler := billing.NewWebhookHandler(
		exampleWebhookProvider{},
		billing.WebhookProcessorFunc(func(_ context.Context, event billing.WebhookEvent) error {
			processedEventType = event.EventType
			return nil
		}),
	)

	request := httptest.NewRequest(http.MethodPost, "/billing/webhook", strings.NewReader(`{"ok":true}`))
	request.Header.Set("X-Test-Signature", "ok")

	responseRecorder := httptest.NewRecorder()
	handler.ServeHTTP(responseRecorder, request)

	fmt.Println(responseRecorder.Code, strings.TrimSpace(responseRecorder.Body.String()), processedEventType)

}
Output:
200 ok subscription.updated

func NewWebhookHandler

func NewWebhookHandler(provider WebhookProvider, processor WebhookProcessor) *WebhookHandler

NewWebhookHandler builds a handler for provider webhooks. Non-retryable processor failures are accepted and logged so providers do not retry events that can never succeed.

func (*WebhookHandler) ServeHTTP

func (handler *WebhookHandler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request)

type WebhookProcessor

type WebhookProcessor interface {
	Process(context.Context, WebhookEvent) error
}

WebhookProcessor handles a normalized webhook event.

func NewCreditsWebhookProcessor

func NewCreditsWebhookProcessor(
	granter CreditGranter,
	resolver WebhookGrantResolver,
) (WebhookProcessor, error)

NewCreditsWebhookProcessor builds a webhook processor that applies resolved grants through an application-owned CreditGranter implementation.

func NewSubscriptionStatusWebhookProcessor

func NewSubscriptionStatusWebhookProcessor(
	provider CommerceProvider,
	stateRepository SubscriptionStateRepository,
) (WebhookProcessor, error)

func NewWebhookProcessorChain

func NewWebhookProcessorChain(processors ...WebhookProcessor) WebhookProcessor

NewWebhookProcessorChain combines multiple processors into a single WebhookProcessor that executes them in order.

type WebhookProcessorFunc

type WebhookProcessorFunc func(context.Context, WebhookEvent) error

WebhookProcessorFunc adapts a function into a WebhookProcessor.

func (WebhookProcessorFunc) Process

func (function WebhookProcessorFunc) Process(ctx context.Context, event WebhookEvent) error

type WebhookProvider

type WebhookProvider interface {
	Code() string
	SignatureHeaderName() string
	VerifySignature(signatureHeader string, payload []byte) error
	ParseWebhookEvent(payload []byte) (WebhookEventMetadata, error)
}

WebhookProvider verifies webhook signatures and parses provider-native payloads into package-level event metadata.

Jump to

Keyboard shortcuts

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