Files
terminalShop/main.go
marcus06 bb960c31f8 feat(sca): sweep abandoned 3ds requires_action orders to failed (#37)
* feat(sca): sweep abandoned 3ds requires_action orders to failed

* fix(sca): address pr review on abandoned 3ds sweep

- Drop stripe.Key write in ReconcileStale3DSOrders; wire once in api/main.go after cfg.Load so concurrent reconcilers don't
race the SDK global.
- Add empty-PI short-circuit before paymentintent.Get to bury a future regression in a slog.Warn instead of a Stripe 4xx.
- Swap fmt.Printf for slog.Warn with k/v attrs in loadAbandoned3DSThresholdMinutes so Loki sees structured fields instead of
 literal %q/%d.
- Comment the updated_at predicate (RetryAuth bumps it via GORM auto-stamp, intentional grace-window extension) + the
conditional-UPDATE/map-form choice.
- Tests: processing/requires_capture skip, unknown-status skip, stripe-error skip, empty-PI skip, full env-parse table for
loadAbandoned3DSThresholdMinutes.

* fix(stripe): wire key once at init drop per-call writes

* refactor(stripe): drop dead stripeKey fields and pre-call sdk params
2026-06-07 18:16:33 +02:00

110 lines
2.6 KiB
Go

// version: 0.1.1
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
tea "charm.land/bubbletea/v2"
"charm.land/wish/v2"
"charm.land/wish/v2/bubbletea"
"charm.land/wish/v2/logging"
"github.com/charmbracelet/ssh"
"github.com/stripe/stripe-go/v78"
"terminalShop/pkg/auth"
"terminalShop/pkg/config"
"terminalShop/pkg/tui"
)
func listenHost() string {
if h := os.Getenv("SSH_HOST"); h != "" {
return h
}
return "localhost"
}
var (
apiURL string
authFingerprintKey string
)
func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
pubKey := s.PublicKey()
var fingerprint, pubKeyStr string
if pubKey != nil {
fingerprint = auth.GetSSHKeyFingerprint(pubKey)
pubKeyStr = auth.FormatSSHPublicKey(pubKey)
}
m := tui.NewModelWithAuth(fingerprint, pubKeyStr, apiURL, authFingerprintKey)
return m, nil
}
func main() {
// Load configuration
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Set API URL and auth key from config for TUI connections
apiURL = cfg.APIURL
authFingerprintKey = cfg.AuthFingerprintKey
// Wire the publishable key into stripe-go's package-level global once.
// TUI uses the legacy Tokens API (stripetoken.New) which accepts the
// publishable key; per-session writes would race across concurrent SSH
// sessions even though they'd all write the same value.
// NOTE: assumes single-process — if you ever embed both the SSH and API
// binaries together, the last writer wins and one of them silently breaks.
stripe.Key = cfg.StripePublicKey
log.Printf("TUI will connect to API at: %s", apiURL)
// Create SSH server
host := listenHost()
p := cfg.SSHPort
s, err := wish.NewServer(
wish.WithAddress(fmt.Sprintf("%s:%s", host, p)),
wish.WithHostKeyPath(".ssh/term_info_ed25519"),
wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
// Allow all public keys (we handle auth in the tea handler)
return true
}),
wish.WithMiddleware(
bubbletea.Middleware(teaHandler),
logging.Middleware(),
),
)
if err != nil {
log.Fatalln(err)
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
log.Printf("Starting SSH server on %s:%s", host, p)
log.Printf("Connect with: ssh %s -p %s", host, p)
go func() {
if err = s.ListenAndServe(); err != nil {
log.Fatalln(err)
}
}()
<-done
log.Println("Stopping SSH server")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := s.Shutdown(ctx); err != nil {
log.Fatalln(err)
}
}