package main import ( "context" "encoding/json" "flag" "fmt" "os" "time" "aigw/internal/billing" ) func main() { databaseURLEnv := flag.String("database-url-env", "AIGW_DATABASE_URL", "environment variable containing the PostgreSQL URL") stripeKeyEnv := flag.String("stripe-key-env", "AIGW_STRIPE_API_KEY", "environment variable containing the Stripe restricted key") currency := flag.String("currency", "usd", "wallet currency") limit := flag.Int("limit", 200, "maximum recent top-up orders to reconcile") resolveMissing := flag.Bool("resolve-confirmed-missing", false, "close uncredited pending orders whose Stripe Sessions are confirmed missing") resolutionReason := flag.String("resolution-reason", "maintenance reconciliation confirmed the uncredited Checkout Session is absent from the configured Stripe account", "auditable reason for resolving missing orders") flag.Parse() databaseURL, stripeKey := os.Getenv(*databaseURLEnv), os.Getenv(*stripeKeyEnv) if databaseURL == "" || stripeKey == "" { fmt.Fprintln(os.Stderr, "database and Stripe key environment variables are required") os.Exit(1) } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) defer cancel() service, err := billing.New(ctx, billing.Options{DatabaseURL: databaseURL, Currency: *currency, StripeEnabled: true, StripeAPIKey: stripeKey}) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer service.Close() result, err := service.Reconcile(ctx, *limit) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if *resolveMissing { for _, mismatch := range result.Mismatches { if mismatch["type"] != "stripe_session_missing" { continue } orderID, _ := mismatch["order_id"].(string) order, getErr := service.GetTopUpOrder(ctx, "", orderID) if getErr != nil { fmt.Fprintln(os.Stderr, getErr) os.Exit(1) } var resolveErr error if order.Status == "paid" { _, resolveErr = service.ReverseMissingTopUpCredit(ctx, order.TenantID, order.ID, billing.ResolveMissingTopUpInput{Reason: *resolutionReason}, billing.ResolutionActor{ID: "reconcile-billing", Type: "maintenance"}) } else { _, resolveErr = service.ResolveMissingTopUp(ctx, order.TenantID, order.ID, billing.ResolveMissingTopUpInput{Reason: *resolutionReason}, billing.ResolutionActor{ID: "reconcile-billing", Type: "maintenance"}) } if resolveErr != nil { fmt.Fprintln(os.Stderr, resolveErr) os.Exit(1) } } result, err = service.Reconcile(ctx, *limit) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } encoder := json.NewEncoder(os.Stdout) encoder.SetIndent("", " ") if err := encoder.Encode(result); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if result.MismatchCount > 0 { os.Exit(2) } }