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:
- Build a provider with NewPaddleProvider or NewStripeProvider using a data-driven catalog of plans and packs.
- Call Migrate and create a SubscriptionStateRepository.
- Build a subscription-state webhook processor with NewSubscriptionStatusWebhookProcessor.
- Optionally add a credit-grant processor with NewCreditsWebhookProcessor if billing events need to grant app-specific credits.
- Combine processors with NewWebhookProcessorChain and expose NewWebhookHandler.
- 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 ¶
- Constants
- Variables
- func Migrate(ctx context.Context, database *gorm.DB) error
- func NormalizePackCode(rawPackCode string) string
- func NormalizePurchaseKind(rawPurchaseKind string) string
- func PackLabelForCode(rawPackCode string) string
- type CatalogValidationProvider
- type CheckoutEventStatus
- type CheckoutEventStatusProvider
- type CheckoutReconcileProvider
- type CheckoutSession
- type CommerceProvider
- type CreditGrantInput
- type CreditGranter
- type CustomerContext
- type HTTPErrorDescriptor
- type PackCatalogItem
- type PaddleCommerceClient
- type PaddlePriceBillingCycle
- type PaddlePriceDetails
- type PaddleProvider
- func (provider *PaddleProvider) BuildCheckoutReconcileEvent(ctx context.Context, transactionID string) (WebhookEvent, string, error)
- func (provider *PaddleProvider) BuildUserSyncEvents(ctx context.Context, userEmail string) ([]WebhookEvent, error)
- func (provider *PaddleProvider) ClientToken() string
- func (provider *PaddleProvider) Code() string
- func (provider *PaddleProvider) CreateCustomerPortalSession(ctx context.Context, userEmail string) (PortalSession, error)
- func (provider *PaddleProvider) CreateSubscriptionCheckout(ctx context.Context, customer CustomerContext, planCode string) (CheckoutSession, error)
- func (provider *PaddleProvider) CreateTopUpCheckout(ctx context.Context, customer CustomerContext, packCode string) (CheckoutSession, error)
- func (provider *PaddleProvider) Environment() string
- func (provider *PaddleProvider) InspectSubscriptions(ctx context.Context, userEmail string) ([]ProviderSubscription, error)
- func (provider *PaddleProvider) NewSubscriptionStatusWebhookProcessor(stateRepository SubscriptionStateRepository) (WebhookProcessor, error)
- func (provider *PaddleProvider) NewWebhookGrantResolver() (WebhookGrantResolver, error)
- func (provider *PaddleProvider) ParseWebhookEvent(payload []byte) (WebhookEventMetadata, error)
- func (provider *PaddleProvider) PublicConfig() PublicConfig
- func (provider *PaddleProvider) ResolveCheckoutEventStatus(eventType string) CheckoutEventStatus
- func (provider *PaddleProvider) SignatureHeaderName() string
- func (provider *PaddleProvider) SubscriptionPlans() []SubscriptionPlan
- func (provider *PaddleProvider) TopUpPacks() []TopUpPack
- func (provider *PaddleProvider) ValidateCatalog(ctx context.Context) error
- func (provider *PaddleProvider) VerifySignature(signatureHeader string, payload []byte) error
- type PaddleProviderSettings
- type PaddleSignatureVerifier
- type PaddleSubscriptionWebhookData
- type PaddleSubscriptionWebhookItem
- type PaddleSubscriptionWebhookItemPrice
- type PaddleSubscriptionWebhookPayload
- type PaddleTransactionCompletedLineDetails
- type PaddleTransactionCompletedLineItem
- type PaddleTransactionCompletedLineItemPrice
- type PaddleTransactionCompletedWebhookData
- type PaddleTransactionCompletedWebhookPayload
- type PaddleTransactionInput
- type PaddleWebhookEnvelope
- type PaddleWebhookVerifier
- type PlanCatalogItem
- type PortalSession
- type Provider
- type ProviderSubscription
- type PublicConfig
- type Service
- func (service *Service) CreatePortalSession(ctx context.Context, userEmail string) (PortalSession, error)
- func (service *Service) CreateSubscriptionCheckout(ctx context.Context, customer CustomerContext, planCode string) (CheckoutSession, error)
- func (service *Service) CreateTopUpCheckout(ctx context.Context, customer CustomerContext, packCode string) (CheckoutSession, error)
- func (service *Service) GetSubscriptionSummary(ctx context.Context, userEmail string) (SubscriptionSummary, error)
- func (service *Service) ProviderCode() string
- func (service *Service) ReconcileCheckout(ctx context.Context, userEmail string, transactionID string) error
- func (service *Service) SyncUserBillingEvents(ctx context.Context, userEmail string) error
- func (service *Service) WithTopUpEligibilityPolicy(policy TopUpEligibilityPolicy) *Service
- type StripeCheckoutSessionInput
- type StripeCheckoutSessionWebhookData
- type StripeCheckoutSessionWebhookPayload
- type StripeCheckoutSessionWebhookPayloadData
- type StripeCommerceClient
- type StripePortalSessionInput
- type StripePriceRecurring
- type StripePriceResponse
- type StripeProvider
- func (provider *StripeProvider) BuildCheckoutReconcileEvent(ctx context.Context, transactionID string) (WebhookEvent, string, error)
- func (provider *StripeProvider) BuildUserSyncEvents(ctx context.Context, userEmail string) ([]WebhookEvent, error)
- func (provider *StripeProvider) Code() string
- func (provider *StripeProvider) CreateCustomerPortalSession(ctx context.Context, userEmail string) (PortalSession, error)
- func (provider *StripeProvider) CreateSubscriptionCheckout(ctx context.Context, customer CustomerContext, planCode string) (CheckoutSession, error)
- func (provider *StripeProvider) CreateTopUpCheckout(ctx context.Context, customer CustomerContext, packCode string) (CheckoutSession, error)
- func (provider *StripeProvider) InspectSubscriptions(ctx context.Context, userEmail string) ([]ProviderSubscription, error)
- func (provider *StripeProvider) NewSubscriptionStatusWebhookProcessor(stateRepository SubscriptionStateRepository) (WebhookProcessor, error)
- func (provider *StripeProvider) NewWebhookGrantResolver() (WebhookGrantResolver, error)
- func (provider *StripeProvider) ParseWebhookEvent(payload []byte) (WebhookEventMetadata, error)
- func (provider *StripeProvider) PublicConfig() PublicConfig
- func (provider *StripeProvider) ResolveCheckoutEventStatus(eventType string) CheckoutEventStatus
- func (provider *StripeProvider) SignatureHeaderName() string
- func (provider *StripeProvider) SubscriptionPlans() []SubscriptionPlan
- func (provider *StripeProvider) TopUpPacks() []TopUpPack
- func (provider *StripeProvider) ValidateCatalog(ctx context.Context) error
- func (provider *StripeProvider) VerifySignature(signatureHeader string, payload []byte) error
- type StripeProviderSettings
- type StripeSignatureVerifier
- type StripeSubscriptionItem
- type StripeSubscriptionItemPrice
- type StripeSubscriptionItems
- type StripeSubscriptionWebhookData
- type StripeSubscriptionWebhookPayload
- type StripeSubscriptionWebhookPayloadData
- type StripeWebhookVerifier
- type SubscriptionInspector
- type SubscriptionPlan
- type SubscriptionState
- type SubscriptionStateRepository
- type SubscriptionStateUpsertInput
- type SubscriptionStatusWebhookProcessorProvider
- type SubscriptionSummary
- type TopUpEligibilityPolicy
- type TopUpPack
- type WebhookEvent
- type WebhookEventMetadata
- type WebhookGrant
- type WebhookGrantResolver
- type WebhookGrantResolverProvider
- type WebhookHandler
- type WebhookProcessor
- func NewCreditsWebhookProcessor(granter CreditGranter, resolver WebhookGrantResolver) (WebhookProcessor, error)
- func NewSubscriptionStatusWebhookProcessor(provider CommerceProvider, stateRepository SubscriptionStateRepository) (WebhookProcessor, error)
- func NewWebhookProcessorChain(processors ...WebhookProcessor) WebhookProcessor
- type WebhookProcessorFunc
- type WebhookProvider
Examples ¶
Constants ¶
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 )
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 ¶
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") )
var ( 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") )
var ( 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") )
var ( 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 )
var ( 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") )
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") )
var ( 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") )
var ( 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") )
var ( 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") )
var ( ErrWebhookSubscriptionStateProviderUnsupported = errors.New("billing.webhook.subscription_state.provider.unsupported") )
var ( ErrWebhookGrantResolverProviderUnsupported = errors.New("billing.webhook.grant_resolver.provider.unsupported") 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") )
var ErrDuplicateGrant = errors.New("billing: duplicate grant")
ErrDuplicateGrant signals an idempotent duplicate — not a failure.
var ErrGrantRecipientUnresolved = errors.New("billing: grant recipient unresolved")
ErrGrantRecipientUnresolved signals that the event could not be mapped to a recipient.
var ErrHTTPRetryAttemptsExhausted = errors.New("billing.http.retry.attempts.exhausted")
Functions ¶
func NormalizePackCode ¶
NormalizePackCode lowercases and trims application pack identifiers before they are compared or stored in metadata.
func NormalizePurchaseKind ¶
NormalizePurchaseKind lowercases and trims a purchase-kind marker from provider metadata.
func PackLabelForCode ¶
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 ¶
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 ¶
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
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 ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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)
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 ¶
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.
Source Files
¶
- compat_exports.go
- credit_granter.go
- doc.go
- http_error_descriptor.go
- http_retry_client.go
- http_transport.go
- json.go
- metadata_helpers.go
- paddle_api_client.go
- paddle_provider.go
- paddle_webhook.go
- price_display.go
- provider.go
- service.go
- stripe_api_client.go
- stripe_provider.go
- stripe_webhook.go
- stripe_webhook_processing.go
- subscription_inspection.go
- subscription_state_repository.go
- subscription_status_webhook_processor.go
- webhook_grant_processor.go
- webhook_handler.go
- webhook_processor_chain.go