feat(go): M6.6 — /qr, /sync-bank, /flush-cache, /version pages
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
- GET /qr: Czech QR Platba PNG; ports Python qr_code() exactly
(account validation, amount clamping, * stripping, SPD format)
- GET /sync-bank: Fio sync → infer → cache flush with captured log
- GET+POST /flush-cache: form + action, shows deleted count
- GET /version: JSON alias of /api/version (Python parity)
- FlushCache() added to membership.Sources; wired through api.Handler
- web.ActionHandlers{BankSync} closure-based dep injection for sync
- New dep: github.com/skip2/go-qrcode
- TestQRBuildSPD (9 cases), TestServeQR, TestServeFlushCache{GET,POST},
TestServeSync, TestServeVersion added
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
50
go/internal/web/qr.go
Normal file
50
go/internal/web/qr.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
var validAccount = regexp.MustCompile(`^[A-Z]{2}\d{2,34}$|^\d{1,16}/\d{4}$`)
|
||||
|
||||
// BuildSPD builds a Czech QR Platba SPD string, matching Python's qr_code handler.
|
||||
// Invalid account falls back to defaultAccount.
|
||||
// Amount is clamped to [0, 10_000_000]; non-numeric input becomes "0.00".
|
||||
// Message is truncated to 60 runes and stripped of '*' characters.
|
||||
func BuildSPD(account, amount, message, defaultAccount string) string {
|
||||
if !validAccount.MatchString(account) {
|
||||
account = defaultAccount
|
||||
}
|
||||
|
||||
var amtStr string
|
||||
var f float64
|
||||
if _, err := fmt.Sscanf(amount, "%f", &f); err != nil || f < 0 || f > 10_000_000 {
|
||||
amtStr = "0.00"
|
||||
} else {
|
||||
amtStr = fmt.Sprintf("%.2f", f)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(message) > 60 {
|
||||
runes := []rune(message)
|
||||
message = string(runes[:60])
|
||||
}
|
||||
message = strings.ReplaceAll(message, "*", "")
|
||||
|
||||
var accStr string
|
||||
if parts := strings.SplitN(account, "/", 2); len(parts) == 2 {
|
||||
accStr = parts[0] + "*BC:" + parts[1]
|
||||
} else {
|
||||
accStr = account
|
||||
}
|
||||
|
||||
return fmt.Sprintf("SPD*1.0*ACC:%s*AM:%s*CC:CZK*MSG:%s", accStr, amtStr, message)
|
||||
}
|
||||
|
||||
// RenderQRCode encodes payload as a PNG QR code (256×256, error correction Medium).
|
||||
func RenderQRCode(payload string) ([]byte, error) {
|
||||
return qrcode.Encode(payload, qrcode.Medium, 256)
|
||||
}
|
||||
Reference in New Issue
Block a user