Files
zeekay 1943f1c093 Hanzo Commerce
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans,
usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0.

Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
2026-09-23 21:52:16 -07:00

92 lines
2.8 KiB
Go

package setupintent
import (
"fmt"
"time"
"github.com/hanzoai/commerce/datastore"
"github.com/hanzoai/commerce/models/mixin"
"github.com/hanzoai/orm"
)
var kind = "setup-intent"
// Status represents the state of a SetupIntent.
type Status string
const (
RequiresPaymentMethod Status = "requires_payment_method"
RequiresConfirmation Status = "requires_confirmation"
RequiresAction Status = "requires_action"
Processing Status = "processing"
Succeeded Status = "succeeded"
Canceled Status = "canceled"
)
func init() { orm.Register[SetupIntent](kind) }
// SetupIntent represents a flow to save a payment method for future use.
type SetupIntent struct {
mixin.Model[SetupIntent]
CustomerId string `json:"customerId,omitempty"`
PaymentMethodId string `json:"paymentMethodId,omitempty"`
Status Status `json:"status"`
Usage string `json:"usage,omitempty"`
ProviderRef string `json:"providerRef"`
ProviderType string `json:"providerType"`
CanceledAt time.Time `json:"canceledAt,omitempty"`
CancellationReason string `json:"cancellationReason,omitempty"`
LastError string `json:"lastError,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// Confirm transitions the setup intent to Processing.
func (si *SetupIntent) Confirm() error {
if si.Status != RequiresConfirmation && si.Status != RequiresPaymentMethod {
return fmt.Errorf("cannot confirm setup intent in status %s", si.Status)
}
if si.PaymentMethodId == "" {
return fmt.Errorf("payment method is required to confirm")
}
si.Status = Processing
return nil
}
// MarkSucceeded marks the setup intent as succeeded.
func (si *SetupIntent) MarkSucceeded(providerRef string) {
si.Status = Succeeded
si.ProviderRef = providerRef
}
// Cancel transitions the setup intent to Canceled.
func (si *SetupIntent) Cancel(reason string) error {
if si.Status == Succeeded || si.Status == Canceled {
return fmt.Errorf("cannot cancel setup intent in status %s", si.Status)
}
si.Status = Canceled
si.CancellationReason = reason
si.CanceledAt = time.Now()
return nil
}
// New creates a new SetupIntent wired to the given datastore.
func New(db *datastore.Datastore) *SetupIntent {
si := new(SetupIntent)
si.Init(db)
si.Parent = db.NewKey("synckey", "", 1, nil)
if si.Status == "" {
si.Status = RequiresPaymentMethod
}
if si.Usage == "" {
si.Usage = "off_session"
}
return si
}
// Query returns a datastore query for setup intents.
func Query(db *datastore.Datastore) datastore.Query {
return db.Query(kind)
}