diff options
| author | Chia <Chia@93.nz> | 2026-08-05 22:01:29 +1200 |
|---|---|---|
| committer | Chia <Chia@93.nz> | 2026-08-05 22:07:50 +1200 |
| commit | eadb2ffe85c43cf6fc741c9823cd28eedb4a844c (patch) | |
| tree | 1aba2536d57360da403aa35c9ced58b615c7064e /cmd/reconcile-billing | |
| parent | cd0dd91ab93653631904f2ea0e574ccde6d60339 (diff) | |
feat: harden prepaid billing and commercial operations
Diffstat (limited to 'cmd/reconcile-billing')
| -rw-r--r-- | cmd/reconcile-billing/main.go | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/cmd/reconcile-billing/main.go b/cmd/reconcile-billing/main.go new file mode 100644 index 0000000..79329ad --- /dev/null +++ b/cmd/reconcile-billing/main.go @@ -0,0 +1,79 @@ +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) + } +} |
