Introduce a commercial license option alongside AGPL-3.0-only, require a CLA for contributors, and document the terms in COMMERCIAL.md and NOTICE. Add a script to stamp SPDX headers on Go files and apply it across the tree.
252 lines
8.1 KiB
Go
252 lines
8.1 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: subscription_scheduled_changes.sql
|
|
|
|
package billing
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const applyScheduledChangesForSubscription = `-- name: ApplyScheduledChangesForSubscription :exec
|
|
UPDATE core.subscription_scheduled_changes
|
|
SET status = 'applied'
|
|
WHERE subscription_id = $1 AND status = 'scheduled'
|
|
`
|
|
|
|
// Webhook-primary closeout: when a subscription actually ends (Stripe fires
|
|
// customer.subscription.deleted at period end), mark its still-pending scheduled
|
|
// changes applied so the intent ledger reflects what happened.
|
|
func (q *Queries) ApplyScheduledChangesForSubscription(ctx context.Context, subscriptionID string) error {
|
|
_, err := q.db.ExecContext(ctx, applyScheduledChangesForSubscription, subscriptionID)
|
|
return err
|
|
}
|
|
|
|
const cancelScheduledChange = `-- name: CancelScheduledChange :one
|
|
UPDATE core.subscription_scheduled_changes
|
|
SET status = 'canceled'
|
|
WHERE scheduled_change_id = $1 AND status = 'scheduled'
|
|
RETURNING scheduled_change_id, subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition, status, created_at, updated_at
|
|
`
|
|
|
|
func (q *Queries) CancelScheduledChange(ctx context.Context, scheduledChangeID string) (SubscriptionScheduledChange, error) {
|
|
row := q.db.QueryRowContext(ctx, cancelScheduledChange, scheduledChangeID)
|
|
var i SubscriptionScheduledChange
|
|
err := row.Scan(
|
|
&i.ScheduledChangeID,
|
|
&i.SubscriptionID,
|
|
&i.ChangeType,
|
|
&i.TargetPriceID,
|
|
&i.TargetQuantity,
|
|
&i.EffectiveAt,
|
|
&i.EffectiveTrigger,
|
|
&i.CreditDisposition,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createScheduledChange = `-- name: CreateScheduledChange :one
|
|
INSERT INTO core.subscription_scheduled_changes
|
|
(subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING scheduled_change_id, subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition, status, created_at, updated_at
|
|
`
|
|
|
|
type CreateScheduledChangeParams struct {
|
|
SubscriptionID string `json:"subscription_id"`
|
|
ChangeType string `json:"change_type"`
|
|
TargetPriceID uuid.NullUUID `json:"target_price_id"`
|
|
TargetQuantity sql.NullInt32 `json:"target_quantity"`
|
|
EffectiveAt time.Time `json:"effective_at"`
|
|
EffectiveTrigger string `json:"effective_trigger"`
|
|
CreditDisposition sql.NullString `json:"credit_disposition"`
|
|
}
|
|
|
|
func (q *Queries) CreateScheduledChange(ctx context.Context, arg CreateScheduledChangeParams) (SubscriptionScheduledChange, error) {
|
|
row := q.db.QueryRowContext(ctx, createScheduledChange,
|
|
arg.SubscriptionID,
|
|
arg.ChangeType,
|
|
arg.TargetPriceID,
|
|
arg.TargetQuantity,
|
|
arg.EffectiveAt,
|
|
arg.EffectiveTrigger,
|
|
arg.CreditDisposition,
|
|
)
|
|
var i SubscriptionScheduledChange
|
|
err := row.Scan(
|
|
&i.ScheduledChangeID,
|
|
&i.SubscriptionID,
|
|
&i.ChangeType,
|
|
&i.TargetPriceID,
|
|
&i.TargetQuantity,
|
|
&i.EffectiveAt,
|
|
&i.EffectiveTrigger,
|
|
&i.CreditDisposition,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getScheduledChangeByID = `-- name: GetScheduledChangeByID :one
|
|
SELECT scheduled_change_id, subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition, status, created_at, updated_at FROM core.subscription_scheduled_changes
|
|
WHERE scheduled_change_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetScheduledChangeByID(ctx context.Context, scheduledChangeID string) (SubscriptionScheduledChange, error) {
|
|
row := q.db.QueryRowContext(ctx, getScheduledChangeByID, scheduledChangeID)
|
|
var i SubscriptionScheduledChange
|
|
err := row.Scan(
|
|
&i.ScheduledChangeID,
|
|
&i.SubscriptionID,
|
|
&i.ChangeType,
|
|
&i.TargetPriceID,
|
|
&i.TargetQuantity,
|
|
&i.EffectiveAt,
|
|
&i.EffectiveTrigger,
|
|
&i.CreditDisposition,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listActiveScheduledChangesBySubscription = `-- name: ListActiveScheduledChangesBySubscription :many
|
|
SELECT scheduled_change_id, subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition, status, created_at, updated_at FROM core.subscription_scheduled_changes
|
|
WHERE subscription_id = $1 AND status = 'scheduled'
|
|
ORDER BY effective_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListActiveScheduledChangesBySubscription(ctx context.Context, subscriptionID string) ([]SubscriptionScheduledChange, error) {
|
|
rows, err := q.db.QueryContext(ctx, listActiveScheduledChangesBySubscription, subscriptionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SubscriptionScheduledChange{}
|
|
for rows.Next() {
|
|
var i SubscriptionScheduledChange
|
|
if err := rows.Scan(
|
|
&i.ScheduledChangeID,
|
|
&i.SubscriptionID,
|
|
&i.ChangeType,
|
|
&i.TargetPriceID,
|
|
&i.TargetQuantity,
|
|
&i.EffectiveAt,
|
|
&i.EffectiveTrigger,
|
|
&i.CreditDisposition,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listDueScheduledChanges = `-- name: ListDueScheduledChanges :many
|
|
SELECT scheduled_change_id, subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition, status, created_at, updated_at FROM core.subscription_scheduled_changes
|
|
WHERE status = 'scheduled' AND effective_at <= NOW()
|
|
ORDER BY effective_at ASC
|
|
`
|
|
|
|
// Sweeper backstop: due, not-yet-fired rows. Concurrency safety comes from the
|
|
// per-row claim (MarkScheduledChangeApplied), not row locking, so a plain read
|
|
// is sufficient.
|
|
func (q *Queries) ListDueScheduledChanges(ctx context.Context) ([]SubscriptionScheduledChange, error) {
|
|
rows, err := q.db.QueryContext(ctx, listDueScheduledChanges)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SubscriptionScheduledChange{}
|
|
for rows.Next() {
|
|
var i SubscriptionScheduledChange
|
|
if err := rows.Scan(
|
|
&i.ScheduledChangeID,
|
|
&i.SubscriptionID,
|
|
&i.ChangeType,
|
|
&i.TargetPriceID,
|
|
&i.TargetQuantity,
|
|
&i.EffectiveAt,
|
|
&i.EffectiveTrigger,
|
|
&i.CreditDisposition,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markScheduledChangeApplied = `-- name: MarkScheduledChangeApplied :one
|
|
UPDATE core.subscription_scheduled_changes
|
|
SET status = 'applied'
|
|
WHERE scheduled_change_id = $1 AND status = 'scheduled'
|
|
RETURNING scheduled_change_id, subscription_id, change_type, target_price_id, target_quantity, effective_at, effective_trigger, credit_disposition, status, created_at, updated_at
|
|
`
|
|
|
|
// Idempotency guard / claim: the status transition scheduled->applied is the
|
|
// claim. A second firer (webhook vs sweeper) gets sql.ErrNoRows and no-ops.
|
|
func (q *Queries) MarkScheduledChangeApplied(ctx context.Context, scheduledChangeID string) (SubscriptionScheduledChange, error) {
|
|
row := q.db.QueryRowContext(ctx, markScheduledChangeApplied, scheduledChangeID)
|
|
var i SubscriptionScheduledChange
|
|
err := row.Scan(
|
|
&i.ScheduledChangeID,
|
|
&i.SubscriptionID,
|
|
&i.ChangeType,
|
|
&i.TargetPriceID,
|
|
&i.TargetQuantity,
|
|
&i.EffectiveAt,
|
|
&i.EffectiveTrigger,
|
|
&i.CreditDisposition,
|
|
&i.Status,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const supersedeActiveScheduledChanges = `-- name: SupersedeActiveScheduledChanges :exec
|
|
UPDATE core.subscription_scheduled_changes
|
|
SET status = 'superseded'
|
|
WHERE subscription_id = $1 AND status = 'scheduled'
|
|
`
|
|
|
|
// A new scheduled change (or an immediate action) supersedes any still-pending
|
|
// intent on the same subscription.
|
|
func (q *Queries) SupersedeActiveScheduledChanges(ctx context.Context, subscriptionID string) error {
|
|
_, err := q.db.ExecContext(ctx, supersedeActiveScheduledChanges, subscriptionID)
|
|
return err
|
|
}
|