From 6c9fd2ed009304dd7eba946e940f26ab39617e77 Mon Sep 17 00:00:00 2001 From: erio Date: Thu, 9 Apr 2026 11:58:49 +0800 Subject: [PATCH] fix: prevent settings save on provider edit, randomize order IDs - Remove saveSettings() call from handleSaveProvider - editing a provider should not save the entire settings form - Change order ID format from sub2_5 to sub2_20250409_5_aB3kX9mQ (prefix + date + dbID + 8-char alphanumeric random) to avoid collisions with external payment providers - parseOrderID supports both legacy and new format --- backend/internal/service/payment_service.go | 30 ++++++++++++++++++--- frontend/src/views/admin/SettingsView.vue | 2 -- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/backend/internal/service/payment_service.go b/backend/internal/service/payment_service.go index d48e6e3c0b..2a9f1ba0f5 100644 --- a/backend/internal/service/payment_service.go +++ b/backend/internal/service/payment_service.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "math/rand/v2" "strconv" "strings" "sync" @@ -48,14 +49,37 @@ const ( // --- Types --- // formatOrderID converts an internal DB order ID to the external order ID sent to payment providers. +// Format: sub2_20250409_5_aB3kX9mQ (prefix + date + dbID + 8-char random) func formatOrderID(id int64) string { - return orderIDPrefix + strconv.FormatInt(id, 10) + date := time.Now().Format("20060102") + rnd := generateRandomString(8) + return orderIDPrefix + date + "_" + strconv.FormatInt(id, 10) + "_" + rnd } -// parseOrderID extracts the internal DB order ID from an external order ID returned by payment providers. +// parseOrderID extracts the internal DB order ID from an external order ID. +// Supports both new format "sub2_20250409_5_aB3kX9mQ" and legacy "sub2_5". func parseOrderID(externalID string) (int64, error) { trimmed := strings.TrimPrefix(externalID, orderIDPrefix) - return strconv.ParseInt(trimmed, 10, 64) + parts := strings.Split(trimmed, "_") + switch len(parts) { + case 1: + // Legacy: sub2_5 + return strconv.ParseInt(parts[0], 10, 64) + case 3: + // New: 20250409_5_aB3kX9mQ → extract middle part + return strconv.ParseInt(parts[1], 10, 64) + default: + return 0, fmt.Errorf("invalid order ID format: %s", externalID) + } +} + +func generateRandomString(n int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, n) + for i := range b { + b[i] = charset[rand.IntN(len(charset))] + } + return string(b) } type CreateOrderRequest struct { diff --git a/frontend/src/views/admin/SettingsView.vue b/frontend/src/views/admin/SettingsView.vue index f88a69d873..a8bb2b9960 100644 --- a/frontend/src/views/admin/SettingsView.vue +++ b/frontend/src/views/admin/SettingsView.vue @@ -2942,8 +2942,6 @@ async function handleSaveProvider(payload: any) { } showProviderDialog.value = false loadProviders() - // Also save the overall settings so payment config changes are persisted together - await saveSettings() } catch (err: unknown) { appStore.showError(extractApiErrorMessage(err, t('common.error'), paymentErrorMap.value)) } finally {