activation

package
v1.21.1 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2026 License: GPL-3.0 Imports: 20 Imported by: 0

Documentation

Overview

Package activation owns the paid-order activation workflow and implements its billing-domain stages: the recharge wallet credit, the referral commission and the final settlement. Each stage is idempotent (inbox marker or status CAS); Workflow sequences them with the stages other modules run. The task adapter only decodes a message and invokes the billing facade, and only the facade may reach this package.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidOrderStatus = errors.New("invalid order status")

ErrInvalidOrderStatus reports a lost Paid->Finished CAS: the order left the Paid state underneath the settlement.

Functions

This section is empty.

Types

type Deps

type Deps struct {
	Orders Orders
	// Store carries the billing-scoped transactions, the wallet view and
	// the inbox markers.
	Store Store
	// Profiles resolves the buyer's referrer and the referrer's commission
	// settings from the identity domain.
	Profiles ProfileReader
	// InvitePolicy snapshots the runtime-mutable site-wide referral
	// fallback (percentage, first-purchase-only).
	InvitePolicy func() (percentage uint8, onlyFirstPurchase bool)
}

Deps declares the subdomain's dependencies; the module facade forwards them from the composition root.

type LegacyGuestCache added in v1.20.3

type LegacyGuestCache interface {
	Get(context.Context, string) *redis.StringCmd
}

type Notifier added in v1.20.3

type Notifier interface {
	NotifyTelegramUser(context.Context, int64, string) error
	NotifyAdminsTelegram(context.Context, string) error
}

type Orders added in v1.21.0

type Orders interface {
	FindOneByOrderNo(ctx context.Context, orderNo string) (*order.Order, error)
}

Orders reads the order an activation stage works on.

type ProfileReader

type ProfileReader interface {
	FindOne(ctx context.Context, id int64) (*user.User, error)
}

ProfileReader is the read-only identity port resolving referral settings; the legacy user repository satisfies it structurally.

type Service

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

Service is the activation entry point used by the billing facade.

func NewService

func NewService(deps Deps) *Service

func (*Service) ActivateRecharge

func (s *Service) ActivateRecharge(ctx context.Context, orderNo string) (int64, error)

ActivateRecharge credits the order's amount to the buyer's wallet exactly once and returns the post-credit balance (re-read on replays) for the caller's notification.

func (*Service) FinalizeOrder

func (s *Service) FinalizeOrder(ctx context.Context, orderNo string) error

FinalizeOrder is the billing-domain settlement: coupon accounting and the Paid -> Finished transition (which appends the order.fulfilled outbox event) commit atomically. Losing the status CAS rolls the coupon count back, so it stays exactly-once.

Known transitional window: an admin closing a Paid order between the fulfillment stage and this CAS leaves the fulfillment committed while the order ends Closed. The pre-split code had the same conflict resolved by row locks; compensation for admin closes of paid orders is a billing concern tracked in ADR-001 step 2.

func (*Service) RefundUnfulfillable added in v1.21.0

func (s *Service) RefundUnfulfillable(ctx context.Context, orderNo string) error

RefundUnfulfillable returns what a paid order collected when the subscription domain refuses to fulfil it for good (its subscription was refunded, stopped or handed to a payment provider between checkout and payment): the payment goes back to the buyer's balance and the gift credit the order held to the gift balance, each with the ledger entry a refund writes, a reserved coupon use is released as a close releases it, and the order ends Closed with its event. Everything commits in one billing transaction with the inbox marker, so a redelivered activation refunds once; the order row lock serializes concurrent deliveries.

func (*Service) RefundUnfulfillableToAccount added in v1.21.0

func (s *Service) RefundUnfulfillableToAccount(ctx context.Context, orderNo string, accountID int64) error

RefundUnfulfillableToAccount refunds a paid guest order whose identity already belongs to account accountID, so the order can open no account of its own: the buyer who paid twice under one identity, or paid for an identity registered meanwhile. The order is bound to that account and its payment returned to the account's wallet in the one transaction that closes it, so a redelivery finds it closed and refunded, never bound but unpaid back.

func (*Service) SettleOrderCommission

func (s *Service) SettleOrderCommission(ctx context.Context, orderNo string, buyerID int64) error

SettleOrderCommission credits the referral commission for a purchase or renewal exactly once. The inbox marker also covers the "no commission applies" outcome so replays skip the referrer lock entirely.

func (*Service) UnfulfillableRefunded added in v1.21.0

func (s *Service) UnfulfillableRefunded(ctx context.Context, orderNo string) (bool, error)

UnfulfillableRefunded reports whether the order was refunded because the subscription domain could not fulfil it.

type Store

type Store interface {
	InBillingTx(ctx context.Context, fn func(repository.BillingStore) error) error
	Inbox() repository.InboxRepo
	Wallet() repository.WalletRepo
}

Store is the narrow persistence surface the activation stages need; the repository store satisfies it structurally.

type SubscriptionFulfiller added in v1.20.3

type SubscriptionFulfiller interface {
	FulfillPaidOrder(context.Context, string) (*subscription.FulfillmentOutcome, error)
}

type Workflow added in v1.20.3

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

Workflow activates a paid order: it has identity create a guest buyer's account, credits a recharge or has subscription fulfil the order, settles the referral commission, finalizes the order and sends the notices. Every stage is idempotent, so a failed activation is retried from the start. A fulfillment the subscription domain refuses for good ends the order with a refund instead (see RefundUnfulfillable).

func NewWorkflow added in v1.20.3

func NewWorkflow(deps WorkflowDeps, stages *Service) *Workflow

func (*Workflow) Activate added in v1.20.3

func (w *Workflow) Activate(ctx context.Context, orderNo string) error

type WorkflowDeps added in v1.20.3

type WorkflowDeps struct {
	Orders        WorkflowOrders
	Profiles      ProfileReader
	GuestAccounts identity.GuestAccounts
	// GuestIdentities is the identity port the guest account stage checks
	// the guest's mailbox against before an account is created, the same
	// port the guest purchase checks; nil skips the check.
	GuestIdentities      portal.GuestAccountReader
	Subscriptions        SubscriptionFulfiller
	LegacyGuestCache     LegacyGuestCache
	Notifications        Notifier
	NotificationsEnabled func() bool
}

type WorkflowOrders added in v1.21.0

type WorkflowOrders interface {
	FindOneByOrderNo(ctx context.Context, orderNo string) (*order.Order, error)
	Update(ctx context.Context, data *order.Order) error
}

WorkflowOrders is the order persistence the workflow uses outside its stages: it reads the paid order and binds a guest order to its account.

Jump to

Keyboard shortcuts

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